Creational Patterns

Builder

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)

Factories

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

Prototype

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

Singleton

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