From ee330dbe355b03ca427926cfd88de875bfaab55b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 12 Apr 2018 21:56:20 +0300 Subject: [PATCH 1/2] Further simplifications to builder pattern --- creational/builder.py | 91 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/creational/builder.py b/creational/builder.py index 75e4f2cb..31d70825 100644 --- a/creational/builder.py +++ b/creational/builder.py @@ -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 -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? @@ -29,21 +35,12 @@ """ -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 @@ -51,45 +48,67 @@ def build_floor(self): 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 From c8b831d469b3a447b3930afd2b37269ffef65d04 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sun, 15 Apr 2018 20:21:19 +0300 Subject: [PATCH 2/2] Fix builder tests --- tests/test_builder.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/tests/test_builder.py b/tests/test_builder.py index 533c11c9..3cad0899 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -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')