Design Pattern: Builder Pattern in Go

Builder pattern is used to abstract the complex objects building process from the client and also this pattern is responsible for building objects step-by-step. Builder pattern also abstract complex object creation from the user.

This article demonstrates Builder pattern implementation in Golang. Check the implementation details and the following examples.

Implementation

Here are the simple steps to implement Builder pattern in Go.

  • Create the item structs and implement the getter methods for the properties.
  • Create a builder interface.
  • Create builder stucts and implement the builder interface.
  • Create a producer to produce classes using the builders.

Check the examples below to see the full implementation.

Examples

Let’s look at a few examples of the builder pattern implementation.

Example #1: Vehicle Builder

Consider a builder that generates objects of vehicles, like, cars, planes, etc.

Car Struct

  • Create file named “car.go”.
  • Declare a struct named “Car”. Define properties for – wheel, engine, seat, door, and interior.
  • Define a method named “NewCar”. Accept params for property values as param. Set the values in the method and then return the new car object.
  • Define getter methods for the struct.
// car.go

package main

import "fmt"

type Car struct {
	wheel    int
	engine   int
	seat     int
	door     int
	interior bool
}

func NewCar(noOfWheel int, noOfEngine int, noOfSeat int, noOfDoor int, interior bool) (car *Car) {
	car = &Car{}
	car.wheel = noOfWheel
	car.engine = noOfEngine
	car.seat = noOfSeat
	car.door = noOfDoor
	car.interior = interior
	return
}

func (car *Car) GetDoor() int {
	return car.door
}

func (car *Car) GetEngine() int {
	return car.engine
}

func (car *Car) GetSeat() int {
	return car.seat
}

func (car *Car) GetWheel() int {
	return car.wheel
}

func (car *Car) IsInterior() bool {
	return car.interior
}

func (car *Car) ToString() string {
	return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v", "Car: Wheel -> ", car.wheel, " | Engine -> ", car.engine, " | Seat -> ", car.seat, " | Door -> ", car.door, " | Interior -> ", car.interior)
}

Plane Struct

  • Create a file named “plane.go”.
  • Declare struct named “Plane” and define properties.
  • Define a method named “NewPlane”. Set the values in the method and then return the new car object.
  • Define getter methods for the struct properties.
// plane.go

package main

import "fmt"

type Plane struct {
	wheel    int
	engine   int
	seat     int
	door     int
	wing     int
	interior bool
}

func NewPlane(noOfWheel int, noOfEngine int, noOfSeat int, noOfDoor int, wing int, interior bool) (plane *Plane) {
	plane = &Plane{}
	plane.wheel = noOfWheel
	plane.engine = noOfEngine
	plane.seat = noOfSeat
	plane.door = noOfDoor
	plane.wing = wing
	plane.interior = interior
	return
}

func (plane *Plane) GetDoor() int {
	return plane.door
}

func (plane *Plane) GetEngine() int {
	return plane.engine
}

func (plane *Plane) GetSeat() int {
	return plane.seat
}

func (plane *Plane) GetWheel() int {
	return plane.wheel
}

func (plane *Plane) GetWing() int {
	return plane.wing
}

func (plane *Plane) IsInterior() bool {
	return plane.interior
}

func (plane *Plane) ToString() string {
	return fmt.Sprintf("%v%v%v%v%v%v%v%v%v%v%v%v", "Plane: Wheel -> ", plane.wheel, " | Engine -> ", plane.engine, " | Seat -> ", plane.seat, " | Door -> ", plane.door, " | Wing: ", plane.wing, " | Interior -> ", plane.interior)
}

Vehicle Builder Interface

  • Create a file named “vehicle_builder.go”.
  • Define an interface “VehicleBuilder”. Define methods for setting/adding elements. This interface will be used the “CarBuilder” and “PlaneBuilder”.
// vehicle_builder.go

package main

type VehicleBuilder interface {
	AddDoor(noOfDoor int)
	AddEngine(noOfEngine int)
	AddInterior()
	AddSeat(noOfSeat int)
	AddWheel(noOfWheel int)
	AddWing(noOfWing int)
}

Car Builder

  • Create a file named “car_builder.go” for the car builder.
  • Define struct “CarBuilder”  and define properties – wheel, engine, seat, etc.
  • Define a method “NewCarBuilder” to generate a new car.
  • Implement the “VehicleBuilder” interface by adding the add methods for “CarBuilder”.
// car_builder.go

package main

import "fmt"

type CarBuilder struct {
	wheel    int
	engine   int
	seat     int
	interior bool
	door     int
}

func NewCarBuilder() (carBuilder *CarBuilder) {
	carBuilder = &CarBuilder{}
	return
}

func (carBuilder *CarBuilder) AddDoor(noOfDoor int) {
	fmt.Printf("Add %d doorn", noOfDoor)
	carBuilder.door += noOfDoor
}

func (carBuilder *CarBuilder) AddEngine(noOfEngine int) {
	fmt.Printf("Add %d enginen", noOfEngine)
	carBuilder.engine += noOfEngine
}

func (carBuilder *CarBuilder) AddInterior() {
	fmt.Println("Add interior")
	carBuilder.interior = true
}

func (carBuilder *CarBuilder) AddSeat(noOfSeat int) {
	fmt.Printf("Add %d seatn", noOfSeat)
	carBuilder.seat = noOfSeat
}

func (carBuilder *CarBuilder) AddWheel(noOfWheel int) {
	fmt.Printf("Add %v wheeln", noOfWheel)
	carBuilder.wheel += noOfWheel
}

func (carBuilder *CarBuilder) AddWing(noOfWings int) {
	panic("Can not add wings")
}

func (carBuilder *CarBuilder) Build() *Car {
	car := NewCar(carBuilder.wheel, carBuilder.engine, carBuilder.seat, carBuilder.door, carBuilder.interior)
	return car
}

Plane Builder

  • Create a file named “palne_builder.go” for the plane builder.
  • Define struct “PlaneBuilder”  and define properties – wheel, engine, seat, etc.
  • Define a method “NewPlaneBuilder” to generate a new car.
  • Implement the “VehicleBuilder” interface by adding the add methods for “PlaneBuilder”.
// plane_builder.go

package main

import "fmt"

type PlaneBuilder struct {
	wheel    int
	engine   int
	seat     int
	interior bool
	door     int
	wing     int
}

func NewPlaneBuilder() (planeBuilder *PlaneBuilder) {
	planeBuilder = &PlaneBuilder{}
	return
}

func (planeBuilder *PlaneBuilder) AddDoor(noOfDoor int) {
	fmt.Printf("Add %d doorn", noOfDoor)
	planeBuilder.door += noOfDoor
}

func (planeBuilder *PlaneBuilder) AddEngine(noOfEngine int) {
	fmt.Printf("Add %d enginen", noOfEngine)
	planeBuilder.engine += noOfEngine
}

func (planeBuilder *PlaneBuilder) AddInterior() {
	fmt.Println("Add interior")
	planeBuilder.interior = true
}

func (planeBuilder *PlaneBuilder) AddSeat(noOfSeat int) {
	fmt.Printf("Add %d Seatn", noOfSeat)
	planeBuilder.seat = noOfSeat
}

func (planeBuilder *PlaneBuilder) AddWheel(noOfWheel int) {
	fmt.Printf("Add %d wheelsn", noOfWheel)
	planeBuilder.wheel += noOfWheel
}

func (planeBuilder *PlaneBuilder) AddWing(noOfWings int) {
	fmt.Printf("Add %d wingn", noOfWings)
	planeBuilder.wing += planeBuilder.wing
}

func (planeBuilder *PlaneBuilder) Build() *Plane {
	plane := NewPlane(planeBuilder.wheel, planeBuilder.engine, planeBuilder.seat, planeBuilder.door, planeBuilder.wing, planeBuilder.interior)
	return plane
}

Vehicle Producer

  • Create a file named “vehicle_producer.go” for the producer.
  • Define “BuildCar” method, which takes “CarBuilder” object as param, sets params, and then returns the object.
  • Also, define “BuildPlane” for the “PlaneBuilder”.
// vehicle_producer.go

package main

type VehicleProducer struct {
}

func NewVehicleProducer() (vehicleProducer *VehicleProducer) {
	vehicleProducer = &VehicleProducer{}
	return
}

func (vehicleProducer *VehicleProducer) BuildCar(carBuilder *CarBuilder) *CarBuilder {
	carBuilder.AddWheel(4)
	carBuilder.AddEngine(1)
	carBuilder.AddDoor(4)
	carBuilder.AddSeat(4)
	carBuilder.AddInterior()
	return carBuilder
}

func (vehicleProducer *VehicleProducer) BuildPlane(planeBuilder *PlaneBuilder) *PlaneBuilder {
	planeBuilder.AddWheel(3)
	planeBuilder.AddEngine(2)
	planeBuilder.AddDoor(4)
	planeBuilder.AddSeat(120)
	planeBuilder.AddInterior()
	planeBuilder.AddWing(2)
	
	return planeBuilder
}

Demo

To use the implementation follow the steps below-

  • Create a file named “main.go” and define the “main” function.
  • Create a new “VehicleProducer”.
  • For the car, generate a “CarBuilder” object using “NewCarBuilder” method.
  • Pass the “CarBuilder” object to “BuildCar” method of producer. That will set all the values to the “CarBuilder” object.
  • Finally, call the “build()” method of the “CarBuilder” object.
  • Follow the same for “Plane”.
// main.go

package main

import "fmt"

func main() {
	vehicleProducer := VehicleProducer{}

	fmt.Println("Building Car:")
	carBuilder := NewCarBuilder()
	vehicleProducer.BuildCar(carBuilder)
	car := carBuilder.Build()

	fmt.Printf("Final Result:n%+vn", *car)

	fmt.Println("Building Plane:")
	planeBuilder := NewPlaneBuilder()
	vehicleProducer.BuildPlane(planeBuilder)
	plane := planeBuilder.Build()

	fmt.Printf("Final Result:n%+v", *plane)
}

Output

We will the following output from the code above:

Building Car:

Add 4 wheel
Add 1 engine
Add 4 door
Add 4 seat
Add interior

Final Result:

{
    wheel:4 
    engine:1 
    seat:4 
    door:4 
    interior:true
}


Building Plane:

Add 3 wheels
Add 2 engine
Add 4 door
Add 120 Seat
Add interior
Add 2 wing

Final Result:

{
    wheel:3 
    engine:2 
    seat:120 
    door:4 
    wing:0 
    interior:true
}

Example #2: Request Builder (Simple Builder)

Let’s consider implementing a request-sending process. With this implementation, we want to build the request in multiple steps and then send the request at the end.

Request Struct

  • Create a file named “request.go”.
  • Declare a “struct” named “Request” and add the required properties(url, requestType, header, body, etc.).
  • Declare constant “ReqeustType” to use as the value for the “requestType” property.
  • Add the “Builder” in “Request” as a composition.
  • Add some utility methods as per requirement (like, send() method here).
// request.go

package main

import "fmt"

type RequestType int

const (
	GET RequestType = iota
	POST
	PUT
	PATCH
	DELETE
)

type Request struct {
	url         string
	requestType RequestType
	header      map[string]string
	body        map[string]string

	Builder
}

func (rcvr *Request) Send() {
	fmt.Println("Sending Request...")
	fmt.Printf("nURL: %v", rcvr.url)
	fmt.Printf("nRequest Type: %v", rcvr.requestType)
	fmt.Printf("nHeaders: %v", rcvr.header)
	fmt.Printf("nBody: %v", rcvr.body)

	// Write code for actually sending the request here
}

Builder Struct

  • Create a file named “builder.go”.
  • Declare a “struct” named “Builder” and add the required properties.
  • Declare methods(SetUrl, SetRequestType, etc.) to set all the properties. Return the builder reference from all the set methods, so that we can chain the method calls.
  • Create a method named “build()”. This method will create a new request object, set all the properties from the values of “Builder” and then return the “Request” object.
// builder.go

package main

type Builder struct {
	url         string
	requestType RequestType
	header      map[string]string
	body        map[string]string
}

func (builder *Builder) SetUrl(url string) *Builder {
	builder.url = url
	return builder
}

func (builder *Builder) SetRequestType(reqeustType RequestType) *Builder {
	builder.requestType = reqeustType
	return builder
}

func (builder *Builder) AddHeader(key, value string) *Builder {
	if builder.header == nil {
		builder.header = make(map[string]string)
	}

	builder.header[key] = value
	return builder
}

func (builder *Builder) AddBody(key, value string) *Builder {
	if builder.body == nil {
		builder.body = make(map[string]string)
	}

	builder.body[key] = value
	return builder
}

func (builder *Builder) Build() (request Request) {
	request = Request{}
	request.url = builder.url
	request.requestType = builder.requestType
	request.header = builder.header
	request.body = builder.body

	return
}

Demo

  • Create a file named “main.go”.
  • Use the “Builder” of the “Request” struct.
  • Call the setting methods (like SetUrl, SetRequest, etc.) to set the values. Call the “Build” method to build the desired object finally. We can use call chaining of the methods because the set methods return “builder” at the end.
  • Finally, call the “Send” method to send the request.
// main.go

package main

func main() {
	builder := (&Request{}).Builder.
		SetUrl("https://bigboxcode.com/request-test").
		SetRequestType(POST).
		AddHeader("X-AUTH-TOKEN", "someTokeHere").
		AddHeader("X-SOME-HEADER", "someRandomHeaderValueHere").
		AddBody("unit_id", "99").
		AddBody("code", "8BI4BO6CO2").
		Build()

	builder.Send()
}

Output

We will get the following output when we run the above code.

Sending Request...

URL: https://bigboxcode.com/request-test
Request Type: 1
Headers: map[X-AUTH-TOKEN:someTokeHere X-SOME-HEADER:someRandomHeaderValueHere]
Body: map[code:8BI4BO6CO2 unit_id:99]

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Builder pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.