Method Chaining In Go
Method chaining is a programming pattern where multiple methods are called on the same object sequentially, each call returning the object itself, allowing for a fluent and readable syntax. This pattern is commonly used to configure objects, build queries, and perform a series of operations in a clean and concise manner.
We can see a below example in go which is used gorm. an ORM library for Golang.
db := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
db.Where("name = ?", "John").Or("name = ?", "Jane").Find(&users)
In this example, Where, Or, and Find are chained together on the db object, creating a more readable and concise query statement.
Lets dive into the gorm codebase to check how method chaining is implemented there. At the time of writing this blog, the methods were here in the chainable_api.go file.
. I’ve mentioned the chain methods of Where and OR below :-
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance()
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: conds})
}
return
}
.....
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance()
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: []clause.Expression{clause.Or(clause.And(conds...))}})
}
return
}
Breakdown of the Function
Function Signature -
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB): This defines a method Where on the DB struct, which takes a query and optional arguments, and returns a pointer to a DB instance.Getting an Instance -
tx = db.getInstance(): This line calls the getInstance method on the db object. The getInstance method likely returns a new instance or a clone of the current DB object, allowing for method chaining without modifying the original object.Building Conditions -
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0: This line calls the BuildCondition method on tx.Statement, passing the query and args. This method processes the query and arguments to build the conditions for the SQL WHERE clause. conds is a slice of conditions that will be used in the WHERE clause. The if statement checks if there are any conditions (len(conds) > 0).Adding the Clause -
tx.Statement.AddClause(clause.Where{Exprs: conds}): If there are conditions, this line adds a Where clause to the statement using the AddClause method. The clause.Where{Exprs: conds} creates a new Where clause with the built conditions.Returning the Instance -
return: This returns the new or modified DB instance tx. This allows for further method chaining on the returned DB instance.
We can take reference from above and try creating our own simpler method chaining implementation.
type Person struct {
Name string
Age int
}
func (p *Person) SetName(name string) *Person {
p.Name = name
return p
}
func (p *Person) SetAge(age int) *Person {
p.Age = age
return p
}
func main() {
p := &Person{}
p.SetName("Alice").SetAge(30)
fmt.Println(p)
}
In this Go example:
SetNamesets the name and returns thePersonobject.SetAgesets the age and returns thePersonobject.- This allows chaining
SetNameandSetAgecalls on thePersonobject.
Benefits of Method Chaining
- Readability: Method chaining provides a clear and readable way to build and configure objects, as each method call builds upon the previous one.
- Fluent API Design: It enables a fluent interface, making the code more expressive and easier to understand at a glance.
- Reduced Boilerplate: It minimizes boilerplate code by allowing multiple operations to be performed in a single statement.