Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 55 additions & 36 deletions creational/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,20 @@
from its actual representation (generally for abstraction).

*What does this example do?
This particular example uses a director function to abstract the
construction of a building. The user specifies a Builder (House or
Flat) and the director specifies the methods in the order necessary,
creating a different building depending on the specification
(from the Builder class).

@author: Diogenes Augusto Fernandes Herminio <diofeher@gmail.com>
https://gist.github.com/420905#file_builder_python.py
The first example achieves this by using an abstract base
class for a building, where the initializer (__init__ method) specifies the
steps needed, and the concrete subclasses implement these steps.

In other programming languages, a more complex arrangement is sometimes
necessary. In particular, you cannot have polymorphic behaviour in a
constructor in C++ - see https://stackoverflow.com/questions/1453131/how-can-i-get-polymorphic-behavior-in-a-c-constructor
- which means this Python technique will not work. The polymorphism
required has to be provided by an external, already constructed
instance of a different class.

In general, in Python this won't be necessary, but a second example showing
this kind of arrangement is also included.

*Where is the pattern used practically?

Expand All @@ -29,67 +35,80 @@
"""


def construct_building(builder):
builder.new_building()
builder.build_floor()
builder.build_size()
return builder.building


# Abstract Builder
class Builder(object):
# Abstract Building
class Building(object):

def __init__(self):
self.building = None

def new_building(self):
self.building = Building()
self.build_floor()
self.build_size()

def build_floor(self):
raise NotImplementedError

def build_size(self):
raise NotImplementedError

# Concrete Builder
def __repr__(self):
return 'Floor: {0.floor} | Size: {0.size}'.format(self)


class BuilderHouse(Builder):
# Concrete Buildings
class House(Building):

def build_floor(self):
self.building.floor = 'One'
self.floor = 'One'

def build_size(self):
self.building.size = 'Big'
self.size = 'Big'


class BuilderFlat(Builder):
class Flat(Building):

def build_floor(self):
self.building.floor = 'More than One'
self.floor = 'More than One'

def build_size(self):
self.building.size = 'Small'
self.size = 'Small'


# Product
class Building(object):
# In some very complex cases, it might be desirable to pull out the building
# logic into another function (or a method on another class), rather than being
# in the base class '__init__'. (This leaves you in the strange situation where
# a concrete class does not have a useful constructor)

def __init__(self):
self.floor = None
self.size = None

class ComplexBuilding(object):
def __repr__(self):
return 'Floor: {0.floor} | Size: {0.size}'.format(self)


class ComplexHouse(ComplexBuilding):
def build_floor(self):
self.floor = 'One'

def build_size(self):
self.size = 'Big and fancy'


def construct_building(cls):
building = cls()
building.build_floor()
building.build_size()
return building


# Client
if __name__ == "__main__":
building = construct_building(BuilderHouse())
print(building)
building = construct_building(BuilderFlat())
print(building)
house = House()
print(house)
flat = Flat()
print(flat)

# Using an external constructor function:
complex_house = construct_building(ComplexHouse)
print(complex_house)

### OUTPUT ###
# Floor: One | Size: Big
# Floor: More than One | Size: Small
# Floor: One | Size: Big and fancy
33 changes: 15 additions & 18 deletions tests/test_builder.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
from creational.builder import construct_building, BuilderHouse, BuilderFlat
from creational.builder import construct_building, House, Flat, ComplexHouse


class TestHouseBuilding(unittest.TestCase):
class TestSimple(unittest.TestCase):

def setUp(self):
self.building = construct_building(BuilderHouse())
def test_house(self):
house = House()
self.assertEqual(house.size, 'Big')
self.assertEqual(house.floor, 'One')

def test_house_size(self):
self.assertEqual(self.building.size, 'Big')
def test_flat(self):
flat = Flat()
self.assertEqual(flat.size, 'Small')
self.assertEqual(flat.floor, 'More than One')

def test_num_floor_in_house(self):
self.assertEqual(self.building.floor, 'One')

class TestComplex(unittest.TestCase):

class TestFlatBuilding(unittest.TestCase):

def setUp(self):
self.building = construct_building(BuilderFlat())

def test_house_size(self):
self.assertEqual(self.building.size, 'Small')

def test_num_floor_in_house(self):
self.assertEqual(self.building.floor, 'More than One')
def test_house(self):
house = construct_building(ComplexHouse)
self.assertEqual(house.size, 'Big and fancy')
self.assertEqual(house.floor, 'One')