Builder Pattern in Go
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 80 of 107.
The Builder Pattern separates the construction of a complex object from its representation. Unlike the Functional Options pattern, the Builder uses a dedicated builder struct with methods that configure the object step by step.
Here's how to implement a builder for an HTTP request:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}Each method returns the builder pointer, enabling method chaining. The Build() method finalizes and returns the constructed object:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()The Builder pattern excels when objects require multiple configuration steps or when you want to enforce a specific construction sequence. It's particularly useful for creating immutable objects or when the construction process itself needs validation.
Challenge
EasyLet's build an email message composer using the Builder Pattern! Email messages have many optional components—recipients, CC, subject, body, attachments—making them a perfect candidate for step-by-step construction with method chaining.
You'll organize your code across two files:
email.go: Define your email structure and its builder.Create an
Emailstruct with these fields:from(string)to(slice of strings)cc(slice of strings)subject(string)body(string)
Create an
EmailBuilderstruct that holds a pointer to an Email being constructed. ImplementNewEmailBuilder()that returns a new builder with an initialized Email (empty slices fortoandcc).Add these builder methods, each returning
*EmailBuilderfor chaining:From(address string)- sets the senderTo(address string)- adds a recipient to thetosliceCC(address string)- adds a recipient to theccsliceSubject(subject string)- sets the subject lineBody(body string)- sets the email body
Add a
Build()method that returns the constructed*Email.Finally, add a
Summary()method on Email that returns a string in this format:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: Build and display email messages using the builder.Read the sender address, then read a count of recipients and each recipient address. Next, read a count of CC recipients and each CC address. Finally, read the subject and body.
Use the EmailBuilder with method chaining to construct the email, then print its summary.
The following inputs will be provided:
- Line 1: Sender address
- Line 2: Number of recipients
- Following lines: Each recipient address
- Next line: Number of CC recipients
- Following lines: Each CC address
- Next line: Subject
- Final line: Body
For example, given:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.Your output should be:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.And given:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.Your output should be:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.Notice how the builder lets you add multiple recipients one at a time through repeated To() calls, and how the pattern keeps the construction process clean and readable even with many optional fields.
Cheat sheet
The Builder Pattern separates the construction of a complex object from its representation using a dedicated builder struct with methods that configure the object step by step.
Basic structure:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}Builder methods return the builder pointer to enable method chaining:
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}The Build() method finalizes and returns the constructed object:
func (b *RequestBuilder) Build() *Request {
return b.request
}Usage with method chaining:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()The Builder pattern is useful when objects require multiple configuration steps, when enforcing a specific construction sequence, for creating immutable objects, or when the construction process needs validation.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Read sender address
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// Read number of recipients
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// Read each recipient address
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// Read number of CC recipients
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// Read each CC address
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// Read subject
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// Read body
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: Use the EmailBuilder with method chaining to construct the email
// Start with NewEmailBuilder(), then chain From(), To(), CC(), Subject(), Body()
// Finally call Build() to get the Email
// TODO: Print the email summary using the Summary() method
}
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