Design Patterns with Python

Introduction to Creational Design Patterns in Python

A design pattern in the software engineering world is a reusable solution to a common problem in the software design. You can think of design pattern as a template for solving a problem, not as a finished design that can be converted to the source code. It all comes from Gang of Four book Design Patterns: Elements of Reusable Object-Oriented Software. Name Gang of Four refers to the four authors of the book Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides (hence Gang of Four). GOF book describes elegant and very simple solutions to specific problems in object-oriented software design. It's twenty-four years after the publication of the book but still, it remains incredibly relevant. In the preface of the book is stated that this book isn't an introduction to object-oriented technology or design but it assumes that you are reasonably proficient in at least one object-oriented programming language. And also that it isn't an advanced technical treatise but a book of design patterns that offers elegant and simple solutions to the problems in the object-oriented world.

In the next few chapters(articles) we will cover three basic kinds of design patterns:

  • Creational

  • Structural

  • Behavioral

All of the examples will be written in python but they apply to other object-oriented languages as well since design patterns are language neutral. Nowadays some design patterns are so common that they're now built into most modern languages.

Creational patterns

Creational design patterns in software engineering world, are design patterns that are responsible for an efficient way of creating objects. This type of patterns adds simplicity to object creation by controlling it. Creational design patterns further can be categorized into Class and Object creational patterns, where Object-creational deal with Object creation and Class-creational deal with class instantiation. We will make an overview of six well-known creational design patterns:

  • Factory Method

  • Abstract Factory

  • Singleton

  • Builder

  • Prototype

  • Object Pool

Factory Method

The Factory Method pattern use factory methods for creating objects without specifying the class of the created object. The main idea of the Factory Method is to define an interface for creating objects and let the subclasses to decide which class they will instantiate.

factory.py

class Car:
  def __init__(self, name, capacity):
    self._name = name
    self._capacity = capacity
  
  def drive(self):
     print("Starting the Car engine!")
  
class Bus:
  def __init__(self, name, capacity):
    self.name = name
    self._capacity = capacity
  
  def drive(self):
    print("Starting the Bus engine!")
  
def get_vehicle(vehicle="Car"):
  '''The Factory method '''
  vehicles = dict(car=Car("BMW", 4), bus=Bus("Yutong", 50))
  
  return vehicles[vehicle]
    

car = get_vehicle('car')
car.drive()

bus = get_vehicle('bus')
bus.drive()

The Factory Method pattern, the way it's implemented here, is slightly different from the factory method in a typical programming language because we are trying to fully take advantage of all the features of Python. In this example get_vehicle function will return Car or Bus object, depending on the provided name. In the future, if a new type of Vehicle needs to be added to the factory method it will be very easy.

Abstract Factory

This design pattern usually is called the factory of factories because it provides an interface for creating a factory of dependent objects.

abstract_factory.py

class Car:
  
  def drive(self):
     print("Starting the Car engine!")
  
class Bus:
  
  def drive(self):
    print("Starting the Bus engine!")

class CarFactory:
    
    def get_vehicle(self):
      ''' Return Car object'''
      return Car()
    

class VehicleStore:
    '''  VehicleStore houses Abstract Factory'''
    
    def __init__(self, vehicle_factory=None):
        self._vehicle_factory = vehicle_factory
        
    
    def buy_vehicle(self):
        vehicle = self._vehicle_factory.get_vehicle()
        vehicle.drive()
        

factory = CarFactory()

store = VehicleStore(factory)

store.buy_vehicle()

In this example, VehicleStore encapsulates object creation, and delegates object creation to factory objects instead of creating them directly. Following the example you can see that buy_vehicle calls CarFactory and return new Car, so adding new Factory such as Bus or Tram concrete factory is not a big problem.

Singleton

You can use Singleton as design pattern when you'd like to allow only one object to be instantiated from a class. As a Python example, we will use a Borg pattern which is a one way to implement singleton behavior in Python. One important thing to note is that in python all instance attributes are stored in a dictionary called dict. Each instance will have own dictionary, so Borg pattern modifies that so all instances can share the same dictionary. That said, you could create as many objects as you want but they will share same state information so we can achieve Singleton behavior.

singleton_borg.py

class Borg(object):
    __shared_state = {}
    
    def __init__(self):
        self.__dict__ = self.__shared_state
               
class Singleton(Borg):
    pass

if __name__ == '__main__':
    obj1 = Singleton()
    obj2 = Singleton()
    
    obj1.name = 'Obj1'
    obj2.name = 'Obj2'
    
    print(obj1.name)
    print(obj2.name)

#OUTPUT:
#Obj2
#Obj2

Even if obj1 and obj2 are different instances they share the same attribute name which value will be Obj2. In Python 3 you can use metaclass syntax to achieve Singleton class, example:

singleton.py

class Singleton(type):

    def __init__(cls, name, bases, attrs, **kwargs):
        super().__init__(name, bases, attrs)
        cls._instance = None

    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class SingletonClass(metaclass=Singleton):
    pass

if __name__ == "__main__":    
    obj1 = SingletonClass()
    obj2 = SingletonClass()
    print(obj1 is obj2)
    print(id(obj1))
    print(id(obj2))
    
#OUTPUT
#True
#140189813742112
#140189813742112

In this case, we will have only one instance from obj1 and obj2(check id), and provide a global point of access to it.

From the two examples, we can notice the main difference is the fact that creating Singleton will always return the same instance, opposite of Borg pattern which will return a new object (but it will share all fields).

Builder

This design pattern separates the construction of complex objects so that the same process can create different representations. The main problem that Builder design pattern solves is that it simplifies the creation of complex objects.

builder.py

class Director():
    '''Controls creation of final product '''
    def __init__(self, builder):
        self._builder = builder
        
    def create_house(self):
        self._builder.create_new_house()
        self._builder.add_basement()
        self._builder.add_rooms()
        self._builder.add_roof()
    
    def get_house(self):
        return self._builder.house

class Builder():
    ''' Abstract Builder'''
    def __init__(self):
        self.house = None
        
    def create_new_house(self):
        self.house = House()     

class BarracaBuilder(Builder):
    '''Concrete Builder that provide parts for building baraca '''
    
    def add_basement(self):
        self.house.basement = 'Cement basement'
        
    def add_roof(self):
        self.house.roof = 'Wood shingl roof'
    
    def add_rooms(self):
        self.house.rooms = 1

class House():
    ''' Product '''
    def __init__(self):
        self.basement = None
        self.roof = None
        self.rooms = None
        
    def __str__(self):
        return 'Building {}, {}, and {} room'.format(self.basement,
                self.roof, self.rooms)

builder = BarracaBuilder()
director = Director(builder)
director.create_house()
house = director.get_house()
print(house)

#OUTPUT
#Building Cement basement, Wood shingl roof, and 1 room

Product class defines the complex object that needs to be created by the builder.

Abstract builder class defines each of the steps that need to be done to create the product. Those steps are abstract since the functionality is carried to subclasses.

ConcreteBuilder class extends Builder and is responsible for the creation of the complex product.

Director controls the flow for generating the final product object. Its constructor is called and calls the specific concrete builder to generate the product.

Prototype

Sometimes the cost of creating new objects is expensive and resource intensive. Cloning the objects, in that case, will be a great idea. The prototype design pattern is the one that does that. Instead of creating a new instance it copies an existing object.

prototype.py

class Prototype(object):

    value = 'default'

    def clone(self, **attrs):
        """Clone a prototype and update dictionary"""
        obj = self.__class__()
        obj.__dict__.update(attrs)
        return obj


class PrototypeDispatcher(object):
    def __init__(self):
        self._objects = {}

    def get_objects(self):
        """Get all objects"""
        return self._objects

    def register_object(self, name, obj):
        """Register an object"""
        self._objects[name] = obj

    def unregister_object(self, name):
        """Unregister an object"""
        del self._objects[name]
    
if __name__ == '__main__':   
    dispatcher = PrototypeDispatcher()
    prototype = Prototype()

    obj1 = prototype.clone()
    obj2 = prototype.clone(value='Obj 2', category='a')
    obj3 = prototype.clone(value='Obj 3', is_checked=True)    
    dispatcher.register_object('default', obj1)
    dispatcher.register_object('object2', obj2)
    dispatcher.register_object('object3', obj3)

    #print objects
    for k,v in dispatcher.get_objects().items():
        print(k + ':' + v.value)

#OUTPUT
#default:default
#object2:Obj 2
#object3:Obj 3

In this example the PrototypeDispatcher allows clients to query it for a prototype before cloning new instance. This is very useful because it makes easier to derive new objects when instances of the class have a few different combinations of state.

Object Pool

The Object Pool design pattern uses a pool of initialized objects that are ready to be used rather than creating a new object all the time. The main idea of an Object Pool is that instead of creating instances of the class you can reuse them by getting them from the pool.

object_pool.py

class Pool:
    '''Manage pool objects'''
    
    def __init__(self, size):
        self._objects = [PoolObject() for i in range(size)]
        
    
    def get_object(self):
        return self._objects.pop()
        
    def release_object(self, obj):
        self._objects.append(obj)
        
class PoolObject:
    pass

if __name__ == "__main__":
    pool = Pool(10)
    obj1 = pool.get_object()
    pool.release_object(obj1)

The Pool class is responsible for acquiring and releasing objects from the pool. In the initialize method it accepts the size parameter for creating a number of objects in the pool. This design pattern is effective in situations where the cost of creating objects is expensive and you need only a few of them.

Use case of Creational Design Patterns

Very often software engineers start out using Factory Method since it is easily customizable and not complicated, but it can evolve toward Builder or Abstract factory as more complex and flexible. Abstract Factory classes can be implemented using Prototype or Factory Methods. Also, Builder, Prototype or Abstract Factory can use Singleton in their implementation. That's why we can say that most of the creational design patterns could be competitors or they could be complementary between them self.