The Builder pattern can either consist of a single contructor or you can opt for a piecewise contruction, the builder provides an API for constructing an object step-by-step. Let see some examples of the Builder Pattern
| Builder example (classic) | # Classic Builder
class Computer:
def __init__(self, serial_number): # constructor
self.serial = serial_number
self.memory = None # in gigabytes
self.hdd = None # in gigabytes
self.gpu = None
def __str__(self): # toString
info = ('Memory: {}GB'.format(self.memory),
'Hard Disk: {}GB'.format(self.hdd),
'Graphics Card: {}'.format(self.gpu)) # tuple
return '\n'.join(info) # join all tuple elements with a newline
class ComputerBuilder:
def __init__(self): # constructor
self.computer = Computer('Gamer PC') # Get a computer Object
def configure_memory(self, amount):
self.computer.memory = amount
def configure_hdd(self, amount):
self.computer.hdd = amount
def configure_gpu(self, gpu_model):
self.computer.gpu = gpu_model
class HardwareEngineer:
def __init__(self): # constructor
self.builder = None
def construct_computer(self, memory, hdd, gpu):
self.builder = ComputerBuilder() # create a computer
[step for step in (self.builder.configure_memory(memory),
self.builder.configure_hdd(hdd),
self.builder.configure_gpu(gpu))]
@property
def computer(self):
return self.builder.computer
def main():
engineer = HardwareEngineer() # create a HardwareEngineer Object
engineer.construct_computer(hdd=500, memory=8, gpu='GeForceGTX 650 Ti') # feed data into HardwareEngineer
# to create a Computer Object
computer = engineer.computer # Get the Computer Object
print(computer) # call Computer toString
if __name__ == '__main__':
main() |
| Builder example (fluent) | # Fluent Builder
class Pizza:
def __init__(self, builder): # constructor
self.garlic = builder.garlic
self.extra_cheese = builder.extra_cheese
def __str__(self): # toString
garlic = 'yes' if self.garlic else 'no'
cheese = 'yes' if self.extra_cheese else 'no'
info = (' Garlic: {}'.format(garlic), ' Extra cheese: {}'.format(cheese))
return '\n'.join(info)
class PizzaBuilder:
def __init__(self):
self.extra_cheese = False
self.garlic = False
def add_garlic(self):
self.garlic = True
return self # this allows you to chain
def add_extra_cheese(self):
self.extra_cheese = True
return self # this allows you to chain
def build(self):
return Pizza(self)
if __name__ == '__main__':
print("Pizza 1:")
pizza1 = Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build()
print(pizza1)
print("\nPizza 2:")
pizza2 = Pizza.PizzaBuilder().add_extra_cheese().build() # you can add or subtract from the chain
print(pizza2) |
I will cover three types of factories, Simple, Factory Method and Abstract Factory, I will give exmaples of above, there are number of reasons to use a factory, the Object creation becomes too complex and the constructor is not descriptive on the type of Object you want to create, you are unable to overload the same set of arguments with different names, so many constructors are created to get around this problem. One further point to make is that Factories should not have any state and thus could be ideal as a Singleton Object (see Singleton Pattern below).
| Factory example (simple) | # Simple Factory
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
@abstractmethod # derived classes must override do_say
def do_say(self):
pass
class Dog(Animal):
def do_say(self):
print(" Bhow Bhow!!\n")
class Cat(Animal):
def do_say(self):
print(" Meow Meow!!\n")
class Bird(Animal): # ERROR - Can't instantiate abstract class Bird with abstract methods do_say
def do_speak(self):
print(" tweat, tweat!!\n")
# forest factory defined
class ForestFactory(object):
def make_sound(self, object_type):
return eval(object_type)().do_say() # will run Dog().do_say() or Cat().do_say()
# client code
if __name__ == '__main__':
ff = ForestFactory()
print("Dog: ")
ff.make_sound("Dog")
print("Cat: ")
ff.make_sound("Cat") |
| Factory example (method) | # Factory Method
from abc import ABCMeta, abstractmethod
class Section(metaclass=ABCMeta):
@abstractmethod
def describe(self):
pass
class PersonalSection(Section):
def describe(self):
print("Personal Section")
class AlbumSection(Section):
def describe(self):
print("Album Section")
class PatentSection(Section):
def describe(self):
print("Patent Section")
class PublicationSection(Section):
def describe(self):
print("Publication Section")
class Profile(metaclass=ABCMeta):
def __init__(self):
self.sections = []
self.createProfile()
@abstractmethod
def createProfile(self):
pass
def getSections(self):
return self.sections
def addSections(self, section):
self.sections.append(section)
class linkedin(Profile):
def createProfile(self):
self.addSections(PersonalSection())
self.addSections(PatentSection())
self.addSections(PublicationSection())
class facebook(Profile):
def createProfile(self):
self.addSections(PersonalSection())
self.addSections(AlbumSection())
if __name__ == '__main__':
profile_type = input("Which Profile you'd like to create? [LinkedIn or FaceBook]")
profile = eval(profile_type.lower())()
print("Creating Profile..", type(profile).__name__)
print("Profile has sections --", profile.getSections()) |
| Factory example (abstract) | # Abstract Factory
from abc import ABCMeta, abstractmethod
class PizzaFactory(metaclass=ABCMeta):
@abstractmethod
def createVegPizza(self): # derived classes must override
pass
@abstractmethod
def createNonVegPizza(self): # derived classes must override
pass
class IndianPizzaFactory(PizzaFactory):
def createVegPizza(self):
return DeluxeVeggiePizza()
def createNonVegPizza(self):
return ChickenPizza()
class USPizzaFactory(PizzaFactory):
def createVegPizza(self):
return MexicanVegPizza()
def createNonVegPizza(self):
return HamPizza()
class VegPizza(metaclass=ABCMeta):
@abstractmethod
def prepare(self, VegPizza): # derived classes must override
pass
class NonVegPizza(metaclass=ABCMeta):
@abstractmethod
def serve(self, VegPizza): # derived classes must override
pass
class DeluxeVeggiePizza(VegPizza):
def prepare(self):
print("Prepare", type(self).__name__)
class ChickenPizza(NonVegPizza):
def serve(self, VegPizza):
print(type(self).__name__, "is served with Chicken on", type(VegPizza).__name__)
class MexicanVegPizza(VegPizza):
def prepare(self):
print("Prepare", type(self).__name__)
class HamPizza(NonVegPizza):
def serve(self, VegPizza):
print(type(self).__name__, "is served with Ham on", type(VegPizza).__name__)
class PizzaStore:
def __init__(self):
pass
def makePizzas(self):
for factory in [IndianPizzaFactory(), USPizzaFactory()]:
self.factory = factory
self.NonVegPizza = self.factory.createNonVegPizza()
self.VegPizza = self.factory.createVegPizza()
self.VegPizza.prepare()
self.NonVegPizza.serve(self.VegPizza)
if __name__ == '__main__':
pizza = PizzaStore()
pizza.makePizzas() |
The Prototype pattern again is for creating Objects, sometimes it's easier to copy an Object than to create one, and thats what the Prototype pattern is about copying objects and then customizing it, sometimes this is referred to as cloning, however one point to make is that a deep copy is performed which means all objects references (recursively) inside the object to be copied are also copied.
| Prototype example | # Prototype is about cloning objects (deep copy)
import copy
from collections import OrderedDict
class Book:
def __init__(self, name, authors, price, **rest):
"""Examples of rest: publisher, length, tags, publication date"""
self.name = name
self.authors = authors
self.price = price # in US dollars
self.__dict__.update(rest)
def __str__(self):
mylist = []
ordered = OrderedDict(sorted(self.__dict__.items()))
for i in ordered.keys():
mylist.append('{}: {}'.format(i, ordered[i]))
if i == 'price':
mylist.append('$')
mylist.append('\n')
return ''.join(mylist)
class Prototype:
def __init__(self):
self.objects = dict()
def register(self, identifier, obj):
self.objects[identifier] = obj
def unregister(self, identifier):
del self.objects[identifier]
def clone(self, identifier, **attr):
found = self.objects.get(identifier)
if not found:
raise ValueError('Incorrect object identifier:{}'.format(identifier))
obj = copy.deepcopy(found)
obj.__dict__.update(attr)
return obj
def main():
b1 = Book('The C Programming Language', ('Brian W. Kernighan', 'Dennis M.Ritchie'), price=118,
publisher='Prentice Hall', length=228, publication_date='1978-02-22', tags=('C',
'programming', 'algorithms',
'data structures'))
prototype = Prototype()
cid = 'k&r-first'
prototype.register(cid, b1)
b2 = prototype.clone(cid, name='The C Programming Language (ANSI)', price=48.99, length=274,
publication_date='1988-04-01', edition=2)
for i in (b1, b2):
print(i)
print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2)))
if __name__ == '__main__':
main() |
The Singleton Pattern basically means that you have one and only one Object in the system, and all other Objects/code will access this one Object, you code it so that you can create one instance of the Object, hence the term Singleton. I will cover three of Singleton
| Singleton example (classic) | # Classic Singleton
class Singleton(object):
# __new__ - when you need to control the creation of a new instance (called first)
# __init_ - when you need to control initialization of a new instance
# cls - represents the class that is needed to be instantiated (cls = class)
def __new__(cls):
if not hasattr(cls, 'instance'): # check to see if singleton has been created
cls.instance = \
super(Singleton, cls).__new__(cls) # if not then create an instance of this object
return cls.instance # if already created return the singleton
# Both Objects should point to the same Singleton Object
s = Singleton()
print("Object created", s)
s1 = Singleton()
print("Object created", s1) |
| Singleton example (lazy) | # Lazy Singleton
class Singleton:
__instance = None
def __init__(self):
if not Singleton.__instance:
print(" __init__ method called..")
else:
print("Instance already created:", self.getInstance())
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = Singleton()
return cls.__instance
s = Singleton() # class initialized, but object not created
print("Object created", Singleton.getInstance()) # Singleton Object gets created here
s1 = Singleton() # instance already created |
| Singleton example (monostate) | # Monostate Singleton - share state
class Borg(object):
_shared_state = {} # private dictionary
# cls - represents the class that is needed to be instantiated (cls = class)
# args* - pass a variable number of arguments to a function
# kwargs - pass a keyword, variable-length argument list
# __dict__ - contains all the attributes defined for the object itself
def __new__(cls, *args, **kwargs):
obj = super(Borg, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_state
return obj
b = Borg()
b1 = Borg()
b.x = 4
print("Borg Object 'b': ", b) # b and b1 are distinct objects
print("Borg Object 'b1': ", b1)
print("Object State 'b':", b.__dict__) # b and b1 share same state
print("Object State 'b1':", b1.__dict__) |