Creating complex objects with many optional parameters can be a daunting task.
The traditional constructor and setter approach can become cumbersome when dealing with objects with many optional parameters.
In this article, we will explore the builder pattern, a creational design pattern that allows for the creation of complex objects with many optional parameters.
We will walk through an example implementation of the builder pattern in Go and discuss how it can be used to create different variations of the same object.
+----------------+ +-------------------+
| Director | directs | Builder |
+----------------+ +-------------------+
| construct() | ------------> | buildPart1() |
| setBuilder() | | buildPart2() |
+----------------+ | getProduct() |
+-------------------+
|
V
+-------------------+
| Product |
+-------------------+
| field1 |
| field2 |
| ... |
+-------------------+
Implementing the builder pattern in go
Here is an example of the builder pattern in golang
type Car struct {
Make string
Model string
Year int
Color string
EngineSize float64
}
type CarBuilder struct {
Car
}
func (cb *CarBuilder) SetMake(make string) *CarBuilder {
cb.Make = make
return cb
}
func (cb *CarBuilder) SetModel(model string) *CarBuilder {
cb.Model = model
return cb
}
func (cb *CarBuilder) SetYear(year int) *CarBuilder {
cb.Year = year
return cb
}
func (cb *CarBuilder) SetColor(color string) *CarBuilder {
cb.Color = color
return cb
}
func (cb *CarBuilder) SetEngineSize(engineSize float64) *CarBuilder {
cb.EngineSize = engineSize
return cb
}
func (cb *CarBuilder) Build() *Car {
return &cb.Car
}
The CarBuilder
struct embeds a Car
object, so all of its fields are available to the builder.
The CarBuilder
struct has methods to set the optional parameters of the Car
object. Each method returns a pointer to the CarBuilder
struct to allow for method chaining.
The Build
method on the CarBuilder
struct returns a pointer to the Car
object that was built.
Here’s an example usage of the CarBuilder
:
carBuilder := &CarBuilder{}
car := carBuilder.
SetMake("Toyota").
SetModel("Corolla").
SetYear(2021).
SetColor("Red").
SetEngineSize(1.8).
Build()
fmt.Printf("Make: %s\n", car.Make) // Output: Make: Toyota
fmt.Printf("Model: %s\n", car.Model) // Output: Model: Corolla
fmt.Printf("Year: %d\n", car.Year) // Output: Year: 2021
fmt.Printf("Color: %s\n", car.Color) // Output: Color: Red
fmt.Printf("Engine Size: %.1f\n", car.EngineSize) // Output: Engine Size: 1.8
In this example, we create a CarBuilder
object and use its methods to set the optional parameters of the Car
object.
Finally, we call the Build
method to get the final Car
object. We then print out the fields of the Car
object to verify that they were set correctly.
Advanced Use Cases of the Builder Pattern in Go
The builder pattern has some advanced use cases that can be useful in certain situations.
We will now cover some advanced use cases of the builder pattern in Go.
Creating a Builder Interface
In the basic example of the builder pattern, we had a single builder struct that was used to build an object.
However, you can create a builder interface that multiple builder structs can implement.
This can be useful when you have different types of objects that need to be built using the same pattern.
Let’s say that you have two types of cars: electric cars and gasoline cars. Both types of cars have the same optional parameters, but they have different required parameters.
In this case, you can create a CarBuilder
interface that specifies the required methods for building a car, and then create two structs that implement the CarBuilder
interface: ElectricCarBuilder
and GasolineCarBuilder
.
type CarBuilder interface {
SetMake(make string) CarBuilder
SetModel(model string) CarBuilder
SetYear(year int) CarBuilder
SetColor(color string) CarBuilder
SetEngineSize(engineSize float64) CarBuilder
Build() Car
}
type ElectricCarBuilder struct {
Car
}
type GasolineCarBuilder struct {
Car
}
Both ElectricCarBuilder
and GasolineCarBuilder
embed the Car
struct and implement the CarBuilder
interface.
They can then have their own implementation of the required methods for building a car.
func (b *ElectricCarBuilder) SetMake(make string) CarBuilder {
b.Make = make
return b
}
func (b *ElectricCarBuilder) SetModel(model string) CarBuilder {
b.Model = model
return b
}
func (b *ElectricCarBuilder) SetYear(year int) CarBuilder {
b.Year = year
return b
}
func (b *ElectricCarBuilder) SetColor(color string) CarBuilder {
b.Color = color
return b
}
func (b *ElectricCarBuilder) SetEngineSize(engineSize float64) CarBuilder {
b.EngineSize = engineSize
return b
}
func (b *ElectricCarBuilder) Build() Car {
return b.Car
}
func (b *GasolineCarBuilder) SetMake(make string) CarBuilder {
b.Make = make
return b
}
func (b *GasolineCarBuilder) SetModel(model string) CarBuilder {
b.Model = model
return b
}
func (b *GasolineCarBuilder) SetYear(year int) CarBuilder {
b.Year = year
return b
}
func (b *GasolineCarBuilder) SetColor(color string) CarBuilder {
b.Color = color
return b
}
func (b *GasolineCarBuilder) SetEngineSize(engineSize float64) CarBuilder {
b.EngineSize = engineSize
return b
}
func (b *GasolineCarBuilder) Build() Car {
return b.Car
}
This is how we can create car using the interface, we can also use the same interface for mocking.
func CreateCar(builder CarBuilder) Car {
return builder.
SetMake("Toyota").
SetModel("Corolla").
SetYear(2022).
SetColor("blue").
SetEngineSize(2.0).
Build()
}
func main() {
electricCarBuilder := &ElectricCarBuilder{}
gasolineCarBuilder := &GasolineCarBuilder{}
electricCar := CreateCar(electricCarBuilder)
gasolineCar := CreateCar(gasolineCarBuilder)
fmt.Printf("Electric car: %+v\n", electricCar)
fmt.Printf("Gasoline car: %+v\n", gasolineCar)
}
In this example, we create an ElectricCarBuilder
and a GasolineCarBuilder
, and use them to create an electric car and a gasoline car, respectively.
The CreateCar
function takes a CarBuilder
interface and sets the required fields using the builder's methods, before finally calling the Build
method to create the Car
object.
Subscribe to my Youtube channel
Subscribe to my youtube channel if you are on the lookout for more such awesome content in video format.
Claps Please !!
If you found this article helpful I would appreciate some claps 👏👏👏👏, it motivates me to write more such useful articles in the future.
Follow me on medium for regular awesome content and insights.
Subscribe to my Newsletter
If you like my content, then consider subscribing to my free newsletter, to get exclusive, educational, technical, interesting and career related content directly delivered to your inbox
Important Links
Thanks for reading the post, be sure to follow the links below for even more awesome content in the future.
Twitter: https://twitter.com/dsysd_dev
Youtube: https://www.youtube.com/@dsysd-dev
Github: https://github.com/dsysd-dev
Medium: https://medium.com/@dsysd-dev
Email: dsysd.mail@gmail.com
Linkedin: https://www.linkedin.com/in/dsysd-dev/
Newsletter: https://dsysd.beehiiv.com/subscribe
Gumroad: https://dsysd.gumroad.com/