Design Pattern Series - Builder (Golang)
Previous post in series Singleton Pattern
When coding in Go, you must have encountered cases where you need to create a struct with a lot of parameters, including optional and required. If you add all of them to a constructor, the code will become difficult to read and prone to errors.
--> After researching, I found the Builder Pattern, and this is how this pattern helps me optimize this problem.
What is the Builder Pattern?
This is a pattern belonging to the Creational Design Patterns group, its main idea includes (I keep it in English to avoid confusion when translating to Vietnamese):
- Separate the construction of a complex object from its representation -> instead of creating a struct with a long constructor, we split the initialization process into steps, and then finally build the complete struct (representation)
- Step-by-step construction -> set each property of a struct sequentially
- Avoid telescoping constructors -> When a struct has too many optional parameters, traditional creation will be a bit difficult to read (NewCar("vinfast", "VF9", "green", "$100000", .....)). With the builder, you avoid having to pass too many parameters at once.
Example without Builder:
car := Car{
Brand: "Toyota",
Model: "Camry",
Color: "Black",
Engine: "V6",
Seats: 5,
Sunroof: true,
}
// or use a constructor
car := NewCar("Toyota", "Camry", "Black", "V6", 5, true)
--> Here, the creation of the struct (Car) and the representation (car) are combined in one place.
- You must know all the parameters.
- Constructors can easily become confusing if there are many optional parameters.
With builder
car := NewCarBuilder().
SetBrand("Toyota").
SetModel("Camry").
SetColor("Black").
SetEngine("V6").
SetSeats(5).
SetSunroof(true).
Build()--> Here:
- Construction process:
SetBrand,SetModel,SetColor… - Representation:
car(an instance ofCar).
That is, you do not directly create a Car, but use a “builder” (CarBuilder) to prepare each part, then call Build() to output the complete object.
So, the Builder Pattern is simply:
- Don't force you to create a struct immediately.
- Allows you to assemble it piece by piece, easy to read, easy to understand.
- Avoid constructor overloading.
--> When to use the the Builder Pattern?
- When a struct has many parameters
- Need more readable and maintainable code
- Need to initialize in different ways
Implement the Builder Pattern in Go
Suppose we need to create a struct for a Car with many different attributes, such as brand, model, color, price, horsepower, material, number of seats, trunk...
Define struct Car and CarBuilder
package main
import "fmt"
// Product: Car
type Car struct {
Brand string
Model string
Color string
Engine string
Seats int
Sunroof bool
}
// Builder
type CarBuilder struct {
brand string
model string
color string
engine string
seats int
sunroof bool
}
func NewCarBuilder() *CarBuilder {
return &CarBuilder{}
}
func (b *CarBuilder) SetBrand(brand string) *CarBuilder {
b.brand = brand
return b
}
func (b *CarBuilder) SetModel(model string) *CarBuilder {
b.model = model
return b
}
func (b *CarBuilder) SetColor(color string) *CarBuilder {
b.color = color
return b
}
func (b *CarBuilder) SetEngine(engine string) *CarBuilder {
b.engine = engine
return b
}
func (b *CarBuilder) SetSeats(seats int) *CarBuilder {
b.seats = seats
return b
}
func (b *CarBuilder) SetSunroof(sunroof bool) *CarBuilder {
b.sunroof = sunroof
return b
}
func (b *CarBuilder) Build() Car {
return Car{
Brand: b.brand,
Model: b.model,
Color: b.color,
Engine: b.engine,
Seats: b.seats,
Sunroof: b.sunroof,
}
}
And uses it
func main() {
car := NewCarBuilder().
SetBrand("Toyota").
SetModel("Camry").
SetColor("Black").
SetEngine("V6").
SetSeats(5).
SetSunroof(true).
Build()
fmt.Printf("%+v\n", car)
}
Output
{Brand:Toyota Model:Camry Color:Black Engine:V6 Seats:5 Sunroof:true}However, the Builder Pattern also has some limitations, as you can see:
- Longer code to define the builder
- Structs with few parameters can cause overkill
Conclude
Builder Pattern in Golang is a good choice when you need to create complex objects with many optional parameters. It makes the code clearer, easier to read, and easier to extend.
Thanks for reading!