In this series, we are discussing Design Patterns. In the previous articles, we have already discussed the Object Factory Design Pattern and the Singleton Design Pattern. After 10 days break, we are back again to explore and implement the Builder Design Pattern in Golang.

Builder design pattern is a creational design pattern that is used to encapsulate the construction logic of an object. This design pattern is used to separate the representation and construction of an object. It, thus, allows the client to construct different objects by following the same process.  This object depends upon the step by step methodology to create an object.

Suppose the class of objects we are concerned with is Cell Phones. Now cell phones have a lot of attributes. Typically the class is extensive. We have different varieties of cell phones having different structures. And there are a lot of attributes. Some cell phones have a camera, others do not. Some cell phones have a Gyroscope, others do not. So we safely conclude that there are a lot of attributes and setting them in the constructor is not the right idea. The builder design pattern comes here to rescue. We define a Build Process, write the concrete implementations and then make a director to call the build process and deliver the product.

Before we began, let us get to know the terminology :

  • Product: The structure defining the product. (In our case the Cell Phone).
  • Build Process: It is the interface that defines the step to be taken for a product to be built. It defines no logic, just the blueprint of the process. It is what to do part of the code, not how to do it.
  • Concrete Implementation: This is how to do part of the code. All the products implement the Build Process to serve their needs. Taking into consideration the cell phone example discussed, a Nokia phone will have a different concrete implementation than the Samsung phone.
  • Director: Director is the client who constructs the product using the builder interface. Its responsibility is to execute the steps defined in the Build Process.

Let us get back to our example of cell phones. We will be considering two cell phones, Nokia 1100 and Samsung Galaxy. We shall also be considering the following features:

  • Camera
  • Dual Sim Slot
  • Torch
  • Colour Display Screen

Let us see how we can use the builder pattern to solve this problem. First of all, let us define the product.

type CellPhone struct {
 Camera bool
 DualSim bool
 Torch bool
 ColorDisplay bool
}

This is the structure that defines our product Cell Phone. Now let us decide the Build Process.

type BuildProcess interface {
	SetCamera() BuildProcess
	SetDualSim() BuildProcess
	SetTorch() BuildProcess
	SetColorDisplay() BuildProcess
	GetCellPhone() CellPhone
}

Now let us make a concrete implementation for the different Cell Phones.

type Nokia struct {
  Phone CellPhone
}

func (n *Nokia) SetCamera() BuildProcess {
	n.Phone.Camera = false
	return n
}

func (n *Nokia) SetDualSim() BuildProcess {
	n.Phone.DualSim = false
	return n
}

func (n *Nokia) SetTorch() BuildProcess {
	n.Phone.Torch = true
	return n
}

func (n *Nokia) SetColorDisplay() BuildProcess {
	n.Phone.ColorDisplay = false
	return n
}

func (n *Nokia) GetCellPhone() CellPhone {
	return n.Phone
}

As clearly visible, we have made a new product Nokia, which is nothing just represents a type of Cell Phone, and implemented the functions of the Build Process, keeping in mind the product we want Nokia to be.

We will follow a similar process for Samsung as well.

type Samsung struct {
	Phone CellPhone
}

func (s *Samsung) SetCamera() BuildProcess {
	s.Phone.Camera = true
	return s
}

func (s *Samsung) SetDualSim() BuildProcess {
	s.Phone.DualSim = true
	return s
}

func (s *Samsung) SetTorch() BuildProcess {
	s.Phone.Torch = false
	return s
}

func (s *Samsung) SetColorDisplay() BuildProcess {
	s.Phone.ColorDisplay = true
	return s
}

func (s *Samsung) GetCellPhone() CellPhone {
	return s.Phone
}


Now let us implement the Director. Director is the code segment that will call these functions step by step to build the product.

type Director struct {
	builder BuildProcess
}

func (d *Director) SetBuilder(b BuildProcess) {
	d.builder = b
}

func (d *Director) Construct() CellPhone {
	d.builder.SetCamera().SetDualSim().SetTorch().SetColorDisplay()
	return d.builder.GetCellPhone()
}




Finally, now let us write the driver code, which initiates the director and fetches the product.

func main() {
	diro := Director{}
	nokiaPhone := &Nokia{}
	diro.SetBuilder(nokiaPhone)
	phone := diro.Construct()
	printProduct(phone)

        samsungPhone := &Samsung{}
	diro.SetBuilder(samsungPhone)
	phone = diro.Construct()
	printProduct(phone)
}

func printProduct(phone CellPhone) {
	fmt.Println("Phone has camera? ", getAns(phone.Camera))
	fmt.Println("Phone has Dual Sim? ", getAns(phone.DualSim))
	fmt.Println("Phone has Torch? ", getAns(phone.Torch))
	fmt.Println("Phone has Color Display? ", getAns(phone.ColorDisplay))
}

func getAns(b bool) string {
	if b {
		return "YES"
	}
	return "NO"
}

And this is it. We were not required to make a fancy constructor to initialize all the attributes. We could skip any attribute we want just by not calling its setter. This can be extended to other cell phones just by adding their concrete implementations. 

Builder Design Pattern in Golang requires you to write more code, but it offers separation of construction from representation, and in the long run, makes the code neat and extendible.


0 Comments

Leave a Reply

Avatar placeholder

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