Design Patterns in Action(2)- Builder

A series of programming design patterns illustration with examples with JavaScript/Python

Photo by Andrew “Donovan” Valdivia on Unsplash

Creational Pattern — Builder

Simply put, Builder is a creational design pattern that decouples the construction of an object from its class representation, so you can use the same construction process to create objects of similar kinds with different steps.

At the first glance, it may seem that this is very similar to the Factory pattern from last blog. It does indeed. The difference is that the Builder pattern applies to objects that are more complex, and the nuances are really in the different construction steps for different sub products. An example will be a HttpRequestPreparation Class that will go through: getUrl-->getHttpMethod-->getParam-->setHeader-->setAuthentication before return an object that is ready to be used in the HttpClient. Different API may have different sets of requirement and hence a complex object like this will fit into the Builder pattern.

The pattern involves 4 participants:

  • Product: The concrete Product being assembled. It will only initiate its attributes to the default value/null, and let the builder to add matching properties later.
  • Builder: Builds the concrete product. Every method will augment one/multiple properties of the Product, and return the Product instance to be chained by next Builder method. It normally finishes with a get_result method that returns the Product instance. It implements the Abstract Builder interface.
  • Abstract Builder: The Interface that the Concrete builder adhere to.
  • Director: Encapsulates the step by step building process from the Builder, so it won’t be exposed to client, finishing with a Builder `get_result` method.

Below is an example in python, again, we are using abstract class to implement the abstract Builder.

from abc import ABCMeta, abstractmethod

class IBread(metaclass=ABCMeta):
@staticmethod
@abstractmethod
def label_bread(kind):
"Bread kind"

@staticmethod
@abstractmethod
def choose_flour(flour_kind):
pass

@staticmethod
@abstractmethod
def wait_for_rise(rise_in_min):
pass

@staticmethod
@abstractmethod
def wait_for_bake(bake_in_min, temp):
pass

@staticmethod
@abstractmethod
def get_result():
pass


class Bread():
def __init__(self, kind=None, flour_kind="white",
rise_in_min=0, bake_in_min=0, temp=200):
self.kind = kind
self.flour_kind = flour_kind
self.rise_in_min = rise_in_min
self.bake_in_min = bake_in_min
self.temp = temp

def who_am_i(self):
return f"I am {self.kind} that is made of {self.flour_kind}"\
f"and is baked under {self.temp} for {self.bake_in_min}!"


class BreadBuilder(IBread):
def __init__(self):
self.bread = Bread()

def label_bread(self, kind):
self.bread.kind = kind
return self

def choose_flour(self, flour_kind):
self.bread.flour_kind = flour_kind
return self

def wait_for_rise(self, rise_in_min):
self.bread.rise_in_min = rise_in_min
return self

def wait_for_bake(self, bake_in_min, temp):
self.bread.bake_in_min = bake_in_min
self.bread.temp = temp
return self

def get_result(self):
return self.bread


class SourdoughDirector:
@staticmethod
def construct():
return BreadBuilder()\
.label_bread("Sourdough")\
.choose_flour("Wholemeal")\
.wait_for_rise(30)\
.wait_for_bake(120, 200)\
.get_result()


class CiabattaDirector:
@staticmethod
def construct():
return BreadBuilder()\
.label_bread("Ciabatta")\
.choose_flour("Wholemeal")\
.wait_for_rise(100)\
.wait_for_bake(90, 220)\
.get_result()


sourdough = SourdoughDirector.construct()

print(sourdough.who_am_i())

And below is an example for JavaScript. Note that comparing to the example above, the Director class itself is a high level one that implements different BuilderConstructions.

class Bread {
constructor(
kind = None,
flour_kind = "white",
rise_in_min = 0,
bake_in_min = 0,
temp = 200
) {
this.kind = kind;
this.flour_kind = flour_kind;
this.rise_in_min = rise_in_min;
this.bake_in_min = bake_in_min;
this.temp = temp;
}

whoAmI() {
return `I am ${this.kind} that is made of ${this.flour_kind} \n
and is baked under ${this.temp} for ${this.bake_in_min}!`;
}
}

class BreadBuilder {
constructor() {
this.bread = new Bread();
}

label_bread(kind) {
this.bread.kind = kind;
return this;
}

choose_flour(flour_kind) {
this.bread.flour_kind = flour_kind;
return this;
}

wait_for_rise(rise_in_min) {
this.bread.rise_in_min = rise_in_min;
return this;
}

wait_for_bake(bake_in_min, temp) {
this.bread.bake_in_min = bake_in_min;
this.bread.temp = temp;
return this;
}

get_result() {
return this.bread;
}
}

class BreadDirector {
constructor(builder = new BreadBuilder()) {
this.builder = builder;
}

constructCiabatta() {
return this.builder
.label_bread("Ciabatta")
.choose_flour("Wholemeal")
.wait_for_rise(100)
.wait_for_bake(90, 220)
.get_result();
}

constructSourdougha() {
return this.builder
.label_bread("Sourdough")
.choose_flour("Wholemeal")
.wait_for_rise(30)
.wait_for_bake(120, 200)
.get_result();
}
}

const sourdough = new BreadDirector().constructSourdougha();
sourdough.whoAmI();

Comparing to the Factory pattern, you can tell the Builder pattern is more complex and difficult to maintain. So it really only fits with quite complex object construction.

That’s so much of it! Happy Reading!

Hi :)