Builder

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 3 minutes

Builder Design Pattern in Python

Builder Design Pattern in Python

Design Patterns Home

What is it?

The Builder Design Pattern simplifies the process of creating composite objects. It is a Creational Design Pattern as it states an efficient way of creating complex objects. Furthermore, it promotes code re-usability, as only small piece of code is required to create another complex object using existing code. It makes use of Inheritance.


Why the need for it: Problem Statement

Consider a complex object, say, a car. A car has many components such as engine, tyres, speedometer, seats etc. Typically, a car object will contain instances of all these components. This leads to an excessive number of constructors and chaotic code. The Builder Pattern resolves this issue and brings order to this chaos by removing the complexity involved. This is achieved by segregating the entire process into four roles.


Terminology

  • Product: Complex object to be made
  • Abstract Builder: provides necessary interfaces required to build the object. It is abstract because it is not instantiated, it is only inherited by the Concrete Builder.
  • Concrete Builder: inherits the Abstract Builder and implements the above interfaces of the Abstract Builder class; provides methods to create components of the product.
  • Director: in charge of creating the product, assembling various components and then delivering it. It uses the concrete builder object.

Pseudo Code

# Product: Complex object to be made
class Car():
def __init__(): 3 dummy variables - engine, tyres & speedometer
def __str__(): returns a string representation of the car object using these 3 variables

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
def __init__(self): dummy car variable set to None
def createNewCar(self): creates a Car object and sets it to local variable car

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
def addEngine()
def addTyres()
def addSpeedometer()

# Director: in charge of building the product using an object of Concrete Builder
class Director():
def __init__(builder): assigns the supplied Concrete Builder object 'builder' to a local variable
def constructCar(): using the 'builder' object, these methods are called: createNewCar(), addEngine(), addTyres(), addSpeedometer()
def getCar(): returns the completely built car

# USIING THE CODE
create an object of ConcreteBuilder
create an object of Director and supply the concreteBuilder object as an argument to it
using the director object, call the constructCar() to make the car
using the directory object, call the getCar() to receive the object and use it as you like

How to implement it

Using the pseudo code above, it is easy to materialize the Builder Pattern using different classes for Product, AbstractBuilder, ConcreteBuilder & Director.

# Basic example of Builder Pattern
# Product to be made: Car
# Components: Engine, Tyres, Speedometer

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {}'.format(self.engine, self.tyres, self.speedometer)

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
def createNewCar(self):
self.car = Car()

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.'''
def addEngine(self):
self.car.engine = "4-stroke"
def addTyres(self):
self.car.tyres = "MRF"
def addSpeedometer(self):
self.car.speedometer = "0-160"

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self):
self._builder.createNewCar()
self._builder.addEngine()
self._builder.addTyres()
self._builder.addSpeedometer()
def getCar(self):
return self._builder.car

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar()
carOne = director.getCar()
print("Details of carOne:", carOne)

### OUTPUT ###
Details of carOne: 4-stroke | MRF | 0-160

Enhanced example

Building on the previous basic example, let's take it one step towards a more realistic implementation by adding component classes of Engine, Tyres and Speedometer. In addition to this, let's give the class Car an attribute 'make', storing the makers brand name. Expand the following code snippet for source code.

# Enhanced example of building a complex object Car
# Changes from basic example:
# 1. Component classes Engine, Tyres, Speedometer
# 2. Addition of the 'make' attribute to the Car class.

# Components of the Product: Engine, Tyres, Speedometer
class Engine:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Tyres:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Speedometer:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.make = None
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {} | {}'.format(self.make, self.engine, self.tyres, self.speedometer)

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
def createNewCar(self):
self.car = Car()

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.'''
def addMake(self):
self.car.make = "Honda"
def addEngine(self):
self.car.engine = Engine("4-stroke")
def addTyres(self):
self.car.tyres = Tyres("MRF")
def addSpeedometer(self):
self.car.speedometer = Speedometer("0-160")

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self):
self._builder.createNewCar()
self._builder.addMake()
self._builder.addEngine()
self._builder.addTyres()
self._builder.addSpeedometer()
def getCar(self):
return self._builder.car

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar()
carOne = director.getCar()
print("Details of carOne:", carOne)

### OUTPUT ###
Details of carOne: Honda | 4-stroke | MRF | 0-160

Let's take it a step further by enabling our user to create different cars by altering the way the constructCar() is called. Expand the following code snippet for source code.

# Further enhanced example of using Builder Pattern to make a complex object Car
# The following code is capable of making many cars with change in how the constructCar() is called by the director
# The constructCar() takes 4 arguments: descriptions of make, engine, tyres & speedometer
# These four arguments are passed on to addMakersName(), addEngine(), addTyres() & addSpeedometer() respectively of Concrete Builder object.
# This enables to make entirely new complex object by tweaking the above four arguments to constructCar()

# Components of the Product: Engine, Tyres, Speedometer
class Engine:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Tyres:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Speedometer:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.make = None
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {} | {}'.format(self.make, self.engine, self.tyres, self.speedometer)

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
def createNewCar(self):
self.car = Car()

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say
that its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.'''
def addMake(self, make):
self.car.make = make
def addEngine(self, engine):
self.car.engine = Engine(engine)
def addTyres(self, tyres):
self.car.tyres = Tyres(tyres)
def addSpeedometer(self, speedometer):
self.car.speedometer = Speedometer(speedometer)

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self, make, engine, tyres, speedometer):
self._builder.createNewCar()
self._builder.addMake(make)
self._builder.addEngine(engine)
self._builder.addTyres(tyres)
self._builder.addSpeedometer(speedometer)
def getCar(self):
return self._builder.car

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar('Honda', '4-stroke', 'MRF', '0-160')
carOne = director.getCar()
print("Details of carOne:", carOne)

director.constructCar('Nissan', '4-stroke', 'Ceat', '0-180')
carTwo = director.getCar()
print("Details of carTwo:", carTwo)

### OUTPUT ###
Details of carOne: Honda | 4-stroke | MRF | 0-160
Details of carTwo: Nissan | 4-stroke | Ceat | 0-180

Here's a demonstration on how to add the capability of creating a different complex object, say, a Computer, to the existing code. Expand the following code snippet for source code.

# To make another complex object, a Computer having the following components: CPU, Mouse, Keyboard, make the following changes:
# Make the component classes: CPU, Mouse, Keyboard
# Make the product class i.e. Computer
# Define a createNewComputer() method in the AbstractBuilder class, similar to createNewCar() method
# Define methods in ConcreteBuilder class to add various components to the product: addCPU(), addMouse(), addKeyboard()
# Call all thses methods in the constructComputer() method in the Director class and provide a getComputer() method to receive an assembled Computer
# Create Computer objects by calling getComputer() method of the Director class.

# Components of the Product: Engine, Tyres, Speedometer
class Engine:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Tyres:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Speedometer:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

# Components of the Product: CPU, Mouse, Keyboard
class CPU:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

class Mouse:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description
class Keyboard:
def __init__(self, description):
self.description = description
def __str__(self):
return self.description

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.make = None
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {} | {}'.format(self.make, self.engine, self.tyres, self.speedometer)

# Product: Complex object to be made.
class Computer:
'''Product: Complex object to be mase.'''
def __init__(self):
self.make = None
self.cpu = None
self.mouse = None
self.keyboard = None
def __str__(self):
return '{} | {} | {} | {}'.format(self.make, self.cpu, self.mouse, self.keyboard)

# Abstract Builder: provides an interface to create a car object and a computer object. It is abstract as it is not instantiated, only inherited by the
# Concrete Builder which uses the interface to create a car and a computer.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
self.computer = None
def createNewCar(self):
self.car = Car()
def createNewComputer(self):
self.computer = Computer()

# Concrete Builder: inherits the Abstract Builder and implements the above interfaces createNewCar and createNewComputer of the
# Abstract Builder class for a car object and a computer object respectively i.e. to say that an object of the Concrete Builder is capable of
# making a new car as well as a new computer by calling the createNewCar() & createNewComputer(); provides methods to create components of these products.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interfaces createNewCar and createNewComputer of the
Abstract Builder class for a car object and a computer object respectively i.e. to say that an object of the Concrete Builder is capable of
making a new car as well as a new computer by calling the createNewCar() & createNewComputer(); provides methods to create components of these products.'''
def addMake(self, make):
self.car.make = make
def addEngine(self, engine):
self.car.engine = Engine(engine)
def addTyres(self, tyres):
self.car.tyres = Tyres(tyres)
def addSpeedometer(self, speedometer):
self.car.speedometer = Speedometer(speedometer)
def addComputerMake(self, make):
self.computer.make = make
def addComputerCPU(self, cpu):
self.computer.cpu = cpu
def addComputerMouse(self, mouse):
self.computer.mouse = mouse
def addComputerKeyboard(self, keyboard):
self.computer.keyboard = keyboard

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self, maker, engine, tyres, speedometer):
self._builder.createNewCar()
self._builder.addMake(maker)
self._builder.addEngine(engine)
self._builder.addTyres(tyres)
self._builder.addSpeedometer(speedometer)
def constructComputer(self, make, cpu, mouse, keyboard):
self._builder.createNewComputer()
self._builder.addComputerMake(make)
self._builder.addComputerCPU(cpu)
self._builder.addComputerMouse(mouse)
self._builder.addComputerKeyboard(keyboard)
def getCar(self):
return self._builder.car
def getComputer(self):
return self._builder.computer

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar('Honda', '4-stroke', 'MRF', '0-160')
carOne = director.getCar()
print("Details of carOne:", carOne)

director.constructCar('Nissan', '4-stroke', 'Ceat', '0-180')
carTwo = director.getCar()
print("Details of carTwo:", carTwo)

director.constructComputer('Dell', 'Celeron', 'Logitech', 'Logitech')
computerOne = director.getComputer()
print("Details of computerOne:", computerOne)

director.constructComputer('HP', 'Intel', 'iBall', 'Dell')
computerTwo = director.getComputer()
print("Details of computerTwo:", computerTwo)

### OUTPUT ###
Details of carOne: Honda | 4-stroke | MRF | 0-160
Details of carTwo: Nissan | 4-stroke | Ceat | 0-180
Details of computerOne: Dell | Celeron | Logitech | Logitech
Details of computerTwo: HP | Intel | iBall | Dell

Walkthrough of implementation # 1

Let's walk through the first example, the basic one. Expand the following code snippet for source code of implementation # 1.

# Basic example of Builder Pattern
# Product to be made: Car
# Components: Engine, Tyres, Speedometer

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {}'.format(self.engine, self.tyres, self.speedometer)

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
def createNewCar(self):
self.car = Car()

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.'''
def addEngine(self):
self.car.engine = "4-stroke"
def addTyres(self):
self.car.tyres = "MRF"
def addSpeedometer(self):
self.car.speedometer = "0-160"

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self):
self._builder.createNewCar()
self._builder.addEngine()
self._builder.addTyres()
self._builder.addSpeedometer()
def getCar(self):
return self._builder.car

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar()
carOne = director.getCar()
print("Details of carOne:", carOne)

### OUTPUT ###
Details of carOne: 4-stroke | MRF | 0-160
  1. We create an object of the ConcreteBuilder
  2. Then, the above object is passed it to the Director, whilst obtaining its object.
  3. Using the director object, we create a car object and assemble it in one method call to constructCar().
  4. Using the getCar() method of the director object, we obtain the ready car and use it as per our needs.

Comparison of code when it is implemented and when it is not

BEFORE BUILDER PATTERN

class Car():
def __init__(self):
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {}'.format(self.engine, self.tyres, self.speedometer)

carOne = Car()
carOne.engine = "4-stroke"
carOne.tyres = "MRF"
carOne.speedometer = "0-160"
print("Details of carOne:", carOne)

carTwo = Car()
carTwo.engine = "4-stroke"
carTwo.tyres = "MRF"
carTwo.speedometer = "0-160"
print("Details of carTwo:", carTwo)

AFTER BUILDER PATTERN

# Basic example of Builder Pattern
# Product to be made: Car
# Components: Engine, Tyres, Speedometer

# Product: Complex object to be made.
class Car():
'''Product: Complex object to be made.'''
def __init__(self):
self.engine = None
self.tyres = None
self.speedometer = None
def __str__(self):
return '{} | {} | {}'.format(self.engine, self.tyres, self.speedometer)

# Abstract Builder: provides an interface to create a car object. It is abstract as it is not instantiated, only inherited by the Concrete Builder
# which uses the interface to create a car.
class AbstractBuilder():
'''Abstract Builder: provides an interface to create a car object.'''
def __init__(self):
self.car = None
def createNewCar(self):
self.car = Car()

# Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
# its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.
class ConcreteBuilder(AbstractBuilder):
'''Concrete Builder: inherits the Abstract Builder and implements the above interface createNewCar of the Abstract Builder class for a car object i.e. to say that
its object is capable of creating a car by calling createNewCar() of AbstractBuilder; provides methods to create components of the product.'''
def addEngine(self):
self.car.engine = "4-stroke"
def addTyres(self):
self.car.tyres = "MRF"
def addSpeedometer(self):
self.car.speedometer = "0-160"

# Director: in charge of building the product using an object of Concrete Builder
class Director():
'''Director: in charge of building the product using an object of Concrete Builder'''
def __init__(self, builder):
self._builder = builder
def constructCar(self):
self._builder.createNewCar()
self._builder.addEngine()
self._builder.addTyres()
self._builder.addSpeedometer()
def getCar(self):
return self._builder.car

concreteBuilder = ConcreteBuilder()
director = Director(concreteBuilder)

director.constructCar()
carOne = director.getCar()
print("Details of carOne:", carOne)

carTwo = director.getCar()
print("Details of carTwo:", carTwo)

One could argue that the code before implementing the Builder Pattern is much more compact. That is because, we are only creating a couple of objects here. Number of lines to create a car, add parts to it, and print its details is 5, while it takes only 2 lines once the setup is done. As the number of objects created is increased, you will see that the latter approach, one with Builder Pattern implemented, is much more economic and systematic. Another thing to note is that we are assigning strings to the components engine, tyres and speedometer. In a real world scenario (something like the most enhanced example in the 'How to Implement It' section), these variables will be assigned component classes such as Engine, Tyres & Speedometer, leading to excessive amount of constructors (Engine(), Tyres() etc.) while creating a car object if the Builder Pattern is not adopted. Hence is advised to use the pattern while creating complex objects.



See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply