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__) |