Method Chaining
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 81 of 107.
Method chaining is a technique where methods return the receiver, allowing multiple method calls to be linked together in a single statement. You've already seen this in the Builder pattern, but it's a standalone concept useful in many contexts.
The key is returning a pointer to the struct from each method:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Now you can chain these calls fluently:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)This reads almost like natural language compared to separate statements. Method chaining works best when operations configure or modify an object without needing to return other values. It's commonly used in query builders, configuration APIs, and test assertion libraries.
Remember that each method must use a pointer receiver and return *StructType for the chain to work properly.
Challenge
EasyLet's build a fluent HTML element builder that uses method chaining to construct HTML tags with attributes and content! This is a practical application of method chaining—you'll create an API that reads almost like natural language when building HTML elements.
You'll organize your code across two files:
element.go: Define your HTML element builder with chainable methods.Create an
Elementstruct that stores the tag name, a map of attributes (string to string), and the inner content. Build these chainable methods that each return*Element:NewElement(tag string)- constructor that creates a new Element with the given tag name and an initialized (empty) attributes mapAttr(key, value string)- adds an attribute to the elementID(id string)- convenience method that sets the "id" attributeClass(class string)- convenience method that sets the "class" attributeContent(text string)- sets the inner content of the element
Add a
Render()method that returns the HTML string. Attributes should appear in alphabetical order by key. The format is:<tag attr1="value1" attr2="value2">content</tag>If there are no attributes, just render
<tag>content</tag>. If there's no content, render<tag attr="value"></tag>.main.go: Build HTML elements using method chaining.Read the tag name, then read a count of operations to perform. Each operation is one of:
idfollowed by the ID valueclassfollowed by the class valueattrfollowed by the attribute name and value (two lines)contentfollowed by the content text
Chain all operations together fluently, then print the rendered HTML.
The following inputs will be provided:
- Line 1: Tag name
- Line 2: Number of operations
- Following lines: Operation type and value(s)
For example, given:
div
3
id
container
class
main-content
content
Hello WorldYour output should be:
<div class="main-content" id="container">Hello World</div>And given:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click hereYour output should be:
<a href="https://example.com" target="_blank">Click here</a>And given:
input
2
attr
type
text
attr
placeholder
Enter nameYour output should be:
<input placeholder="Enter name" type="text"></input>And given:
p
1
content
Simple paragraphYour output should be:
<p>Simple paragraph</p>Notice how method chaining lets you build elements step by step in a readable way. Each method modifies the element and returns it, allowing the next method call to be chained directly.
Cheat sheet
Method chaining is a technique where methods return the receiver (typically *StructType), allowing multiple method calls to be linked together in a single statement.
Each method must use a pointer receiver and return *StructType:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Chain method calls fluently:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)Method chaining works best when operations configure or modify an object without needing to return other values. It's commonly used in query builders, configuration APIs, and test assertion libraries.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Read tag name
scanner.Scan()
tagName := scanner.Text()
// Read number of operations
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Create the element
element := NewElement(tagName)
// Process each operation
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: Based on the operation type, chain the appropriate method
// Operations can be: "id", "class", "attr", or "content"
// - For "id": read the next line as the ID value
// - For "class": read the next line as the class value
// - For "attr": read the next TWO lines (attribute name, then value)
// - For "content": read the next line as the content text
// Use method chaining to build the element fluently
_ = operation // Remove this line when implementing
}
// Print the rendered HTML
fmt.Println(element.Render())
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool12Advanced OOP Concepts
Functional Options PatternBuilder Pattern in GoMethod ChainingType Aliases vs DefinitionsReflection BasicsCode Generation Overview