Design Patterns in Go: The Builder Pattern

Introduction

The builder pattern is a creational design pattern, i.e. it is a pattern for creating or instantiang objects of classes. It is used for breaking down the construction process, into smaller, more manageable and testable steps.

In Go we can use structs and interfaces to implement this pattern.

What does it look like?

The basic builder pattern looks like this:

Let’s break this down into its parts:

  1. The Director. This is the client class for the Builder, and it wants some product to be built.
  2. The Builder interface. This is the generic interface for any Builder, and it contains the methods to build a Product.
  3. The ConcreteBuilder. This is the class where the Product gets built. Because we only use an interface, ConcreteBuilders can be swapped in and out to build different products.
  4. The product we want to build is in the Product class. This could also define an interface, or be referenced through one of its superclasses.

Implementing the building Pattern in Go

First create a new Go project in an empty directory by typing:

go mod init github.com/gobuilderpattern

Open your favorite IDE, and add a main.go file to it. For starters we need these declarations:

package main

import "fmt"

Then we need to define what a very simplified bike looks like, using a Bicycle struct:

type Bicycle struct {
	numberOfWheels int
	bikeType       string
}

Then we need to define how to build a bicycle by defining a BicycleBuilder interface:

type BicycleBuilder interface {
	addWheels() BicycleBuilder
	setType() BicycleBuilder
	getBicycle() Bicycle
}

As you see, all methods return a BicycleBuilder interface, so we chain the calls.

For this example we will define two bicycletypes: ATB and StreetBike. First the ATB:

type ATBBuilder struct {
	vehicle Bicycle
}

func (bikeBuilder ATBBuilder) addWheels() BicycleBuilder {
	bikeBuilder.vehicle.numberOfWheels = 2
	return bikeBuilder
}

func (bikeBuilder ATBBuilder) setType() BicycleBuilder {
	bikeBuilder.vehicle.bikeType = "ATB"
	return bikeBuilder
}

func (bikeBuilder ATBBuilder) getBicycle() Bicycle {
	return bikeBuilder.vehicle
}

Now the StreetBike:

type StreetBikeBuilder struct {
	vehicle Bicycle
}

func (bikeBuilder StreetBikeBuilder) addWheels() BicycleBuilder {
	bikeBuilder.vehicle.numberOfWheels = 3
	return bikeBuilder
}

func (bikeBuilder StreetBikeBuilder) setType() BicycleBuilder {
	bikeBuilder.vehicle.bikeType = "Street"
	return bikeBuilder
}

func (bikeBuilder StreetBikeBuilder) getBicycle() Bicycle {
	return bikeBuilder.vehicle
}

As you see, because we implement the interface BicyleBuilder on both the ATBBuilder and the StreetBikeBuilder, we can return the builder itself.

Now we need an engineer to build our bicycle:

type BikeEngineer struct {
	bikeBuilder BicycleBuilder
}

func (builder BikeEngineer) constructBike() Bicycle {
	return builder.bikeBuilder.addWheels().setType().getBicycle()
}

The BikeEngineer is unaware of the precise type of the bicycle, the class just knows what to put together to build a bicycle.

Now to put it to the test:

func main() {
	builder := StreetBikeBuilder{}

	engineer := BikeEngineer{bikeBuilder: builder}

	bike := engineer.constructBike()
	fmt.Printf("%+v", bike)

}

A breakdown, line by line:

  1. First we instantiate a StreetBikeBuilder.
  2. Then we pass that instance on to our BikeEngineer class.
  3. The engineer class constructs the bicycle.
  4. We print the house. The “%+v” is used for printing out structures with their fieldnames.

Conclusion

The builder pattern is great if you want to hide the construction of certain classes. Of course, you can use struct initialization, but then then the responsibility for the construction of the class would lie with the client, and that is something you not always want.

The implementation in Go took some time, however in the end the implementation turned out to be clear and easy.

Leave a Reply

Your email address will not be published. Required fields are marked *