Skip to content

Commit ec83200

Browse files
authored
Merge pull request faif#223 from spookylukey/pythonic_builder
Further simplifications to builder pattern
2 parents e01181f + c8b831d commit ec83200

File tree

2 files changed

+70
-54
lines changed

2 files changed

+70
-54
lines changed

creational/builder.py

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@
1010
from its actual representation (generally for abstraction).
1111
1212
*What does this example do?
13-
This particular example uses a director function to abstract the
14-
construction of a building. The user specifies a Builder (House or
15-
Flat) and the director specifies the methods in the order necessary,
16-
creating a different building depending on the specification
17-
(from the Builder class).
1813
19-
@author: Diogenes Augusto Fernandes Herminio <diofeher@gmail.com>
20-
https://gist.github.com/420905#file_builder_python.py
14+
The first example achieves this by using an abstract base
15+
class for a building, where the initializer (__init__ method) specifies the
16+
steps needed, and the concrete subclasses implement these steps.
17+
18+
In other programming languages, a more complex arrangement is sometimes
19+
necessary. In particular, you cannot have polymorphic behaviour in a
20+
constructor in C++ - see https://stackoverflow.com/questions/1453131/how-can-i-get-polymorphic-behavior-in-a-c-constructor
21+
- which means this Python technique will not work. The polymorphism
22+
required has to be provided by an external, already constructed
23+
instance of a different class.
24+
25+
In general, in Python this won't be necessary, but a second example showing
26+
this kind of arrangement is also included.
2127
2228
*Where is the pattern used practically?
2329
@@ -29,67 +35,80 @@
2935
"""
3036

3137

32-
def construct_building(builder):
33-
builder.new_building()
34-
builder.build_floor()
35-
builder.build_size()
36-
return builder.building
37-
38-
39-
# Abstract Builder
40-
class Builder(object):
38+
# Abstract Building
39+
class Building(object):
4140

4241
def __init__(self):
43-
self.building = None
44-
45-
def new_building(self):
46-
self.building = Building()
42+
self.build_floor()
43+
self.build_size()
4744

4845
def build_floor(self):
4946
raise NotImplementedError
5047

5148
def build_size(self):
5249
raise NotImplementedError
5350

54-
# Concrete Builder
51+
def __repr__(self):
52+
return 'Floor: {0.floor} | Size: {0.size}'.format(self)
5553

5654

57-
class BuilderHouse(Builder):
55+
# Concrete Buildings
56+
class House(Building):
5857

5958
def build_floor(self):
60-
self.building.floor = 'One'
59+
self.floor = 'One'
6160

6261
def build_size(self):
63-
self.building.size = 'Big'
62+
self.size = 'Big'
6463

6564

66-
class BuilderFlat(Builder):
65+
class Flat(Building):
6766

6867
def build_floor(self):
69-
self.building.floor = 'More than One'
68+
self.floor = 'More than One'
7069

7170
def build_size(self):
72-
self.building.size = 'Small'
71+
self.size = 'Small'
7372

7473

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

78-
def __init__(self):
79-
self.floor = None
80-
self.size = None
8179

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

8584

85+
class ComplexHouse(ComplexBuilding):
86+
def build_floor(self):
87+
self.floor = 'One'
88+
89+
def build_size(self):
90+
self.size = 'Big and fancy'
91+
92+
93+
def construct_building(cls):
94+
building = cls()
95+
building.build_floor()
96+
building.build_size()
97+
return building
98+
99+
86100
# Client
87101
if __name__ == "__main__":
88-
building = construct_building(BuilderHouse())
89-
print(building)
90-
building = construct_building(BuilderFlat())
91-
print(building)
102+
house = House()
103+
print(house)
104+
flat = Flat()
105+
print(flat)
106+
107+
# Using an external constructor function:
108+
complex_house = construct_building(ComplexHouse)
109+
print(complex_house)
92110

93111
### OUTPUT ###
94112
# Floor: One | Size: Big
95113
# Floor: More than One | Size: Small
114+
# Floor: One | Size: Big and fancy

tests/test_builder.py

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33
import unittest
4-
from creational.builder import construct_building, BuilderHouse, BuilderFlat
4+
from creational.builder import construct_building, House, Flat, ComplexHouse
55

66

7-
class TestHouseBuilding(unittest.TestCase):
7+
class TestSimple(unittest.TestCase):
88

9-
def setUp(self):
10-
self.building = construct_building(BuilderHouse())
9+
def test_house(self):
10+
house = House()
11+
self.assertEqual(house.size, 'Big')
12+
self.assertEqual(house.floor, 'One')
1113

12-
def test_house_size(self):
13-
self.assertEqual(self.building.size, 'Big')
14+
def test_flat(self):
15+
flat = Flat()
16+
self.assertEqual(flat.size, 'Small')
17+
self.assertEqual(flat.floor, 'More than One')
1418

15-
def test_num_floor_in_house(self):
16-
self.assertEqual(self.building.floor, 'One')
1719

20+
class TestComplex(unittest.TestCase):
1821

19-
class TestFlatBuilding(unittest.TestCase):
20-
21-
def setUp(self):
22-
self.building = construct_building(BuilderFlat())
23-
24-
def test_house_size(self):
25-
self.assertEqual(self.building.size, 'Small')
26-
27-
def test_num_floor_in_house(self):
28-
self.assertEqual(self.building.floor, 'More than One')
22+
def test_house(self):
23+
house = construct_building(ComplexHouse)
24+
self.assertEqual(house.size, 'Big and fancy')
25+
self.assertEqual(house.floor, 'One')

0 commit comments

Comments
 (0)