×

Design Patterns in Python

Introduction Solid Priciples Design Patterns Python Feature

Design patterns are elegant solutions to repeating problems in software design. These are optimized, reusable solutions to the programming problems that we encounter every day.

Design Pattern Python

Design patterns are programming language independent strategies, that describe how to solve a problem with greater efficiency. These are like descriptions or roadmap, not a particular implementation. Unlike algorithms, design patterns are a structured collection of objects, associations, and actions to accomplish some goal.

The main goal is to understand the purpose and usage of each design pattern in order to pick and implement the correct pattern as needed in real-world situations using Python.

Why use design patterns?

  • Design patterns have predefined set of developments paradigms(i.e rules).
  • Design patterns speed up the development process.
  • Design Patterns reduce code complexity and the risk of code breaks

It’s not mandatory to always implement design patterns in projects, but using design patterns, makes code more flexible, readable, and manageable.

Why Python?

  • Python is an open-source programming language.
  • Python has libraries that provide support for the list of design patterns, mentioned below.
  • The syntax of python are easy to understand and similar to English keywords.

Classification of Design patterns

Design patterns are typically split into three categories:

Creational Design Patterns

Creational design patterns deal with the class or object instantiation. Creational Design Patterns are further divided into Class-creational patterns and Object-creational patterns. Class-creation patterns use inheritance effectively in the instantiation process while Object-creation patterns use delegation(i.e object) effectively to get the job done.

  • Factory Method
  • Abstract Factory Method
  • Builder Method
  • Prototype Method
  • Singleton Method

Structural Design Patterns

Structural design patterns are about organizing different classes members and objects to form larger structures and provide new functionality. Inheritance is used to compose all the interfaces. It also identifies the relationships which led to the simplification of the structure.

  • Adapter Method
  • Bridge Method
  • Composite Method
  • Decorator Method
  • Facade Method
  • Proxy Method
  • FlyWeight Method

Behavioural Design Patterns

Behavioural patterns are about identifying common communication patterns between objects and realizing these patterns. These patterns are concerned with algorithms and the assignment of responsibilities between objects.

  • Chain of Responsibility Method
  • Command Method
  • Iterator Method
  • Mediator Method
  • Memento Method
  • Observer Method
  • State Method
  • Strategy Method
  • Template Method
  • Visitor Method

Generally, the three groups above define how program elements relate to each other, how they are created, and how they communicate with each other.

Design patterns are based on The SOLID Design Principles.

The SOLID Design Principles

The principle of SOLID coding is an acronym originated by Robert C. Martin (Uncle Bob), and it stands for five different conventions of coding.

This set of principles is not a specific ordered procedure, rather it is a collection of best practices. Uncle Bob has introduced various Design Principles and among them, the most popular are the five principles acronymic as SOLID Design Principles that are primarily focused on Object-Oriented Software Designing.

The principles

  • The Single-Responsibility Principle (SRP)
  • The Open-Closed Principle (OCP)
  • The Liskov Substitution Principle (LSP)
  • The Interface Segregation Principle (ISP)
  • The Dependency inversion Principle (DIP)

Single Responsibility Principle(SRP)

A class should have only one reason to change.

SRP simply means, a class should have the primary responsibility of the entity and should not take other responsibilities not related to that entity.

The Single Responsibility Principle asks us not to add additional responsibilities to a class so that we don’t have to modify a class unless there is a change to its primary responsibility.

For example, let’s say we are designing a DailyDiary application that has an interface, which is primarily supposed to handle the responsibility of maintaining Diary entries, adding a new entry(Routine number and Routine), delete an existing entry.

Consider DailyDiary interface below:

class DailyDiary:
    def __init__(self):
        self.entries = []
        self.count = 0

    #Adding entries
    def add_entry(self, entry):        
        self.entries.append(f'{self.count+1}. {entry}')
        self.count += 1
    
    #Deleting entries
    def remove_entry(self):        
        self.count -= 1
        self.entries.remove(self.entries[self.count])
    
    def __str__(self):
        return '\n'.join(self.entries)
    

dd = DailyDiary()
dd.add_entry('Attend a meeting.')
dd.add_entry('Write an article.')

print(dd)

Till now, our interface has exactly implemented the expected features.

Now let’s say that there are two more requirements in the project –  Store the contents of the Diary in a Database and transfer them to a file.

Let’s add two more methods to the interface as below:

class DailyDiary:
    def __init__(self):
        self.entries = []
        self.count = 0

    #Adding entries
    def add_entry(self, entry):        
        self.entries.append(f'{self.count+1}. {entry}')
        self.count += 1
    
    #Deleting entries
    def remove_entry(self):        
        self.count -= 1
        self.entries.remove(self.entries[self.count])
    
    def __str__(self):
        return '\n'.join(self.entries)

    #Storing entries into Database
    def store_to_database(self, dbName):
        pass
    
    #Transfering entries into file
    def save_to_file(self, fName):
        pass

This is where we broke the Single Responsibility Design Principle, now interface is having additional responsibilities other than its primary responsibilities.

If the underlying application changes in a way that it affects storing and transferring functions then those can cause changes to the interface. Thus, the interface is prone to changes due to reasons that are not its primary responsibility.

We can handle violations of SRP by having separate interfaces that would handle storing entries to the database and transferring them to a file.

class store_to_database:
    #Storing entries into Database
    def __init__(self, dbName):
        pass
class save_to_file:
    #Transfering entries into file
    def __init__(self, fName):
        pass

The Single Responsibility Principle(SRP) will make code more robust and flexible as making changes into exiting code will not lead to undefined behaviour. It will reduce the number of changes, be made, significantly, if having several dependent class’s.

Open-Closed Principle(OCP)

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

  • Open for extension, meaning that the class’s behaviour can be extended; and
  • Closed for modification, meaning that the source code is set and cannot be changed.
  • OCP suggests usage of Abstract Base classes and Inheritance.

How Abstract Base classes work in Python

By default, Python does not provide abstract classes. Python comes with a module named abc for defining Abstract Base classes(ABC). ABC works by decorating methods of the base class as abstract and then registering concrete classes as implementations of the abstract base.

A method becomes abstract when decorated with the keyword @abstractmethod.
For example –

from abc import ABC, abstractmethod

#Abstract base class
class AutoMobile(ABC): 
    @abstractmethod
    def RunOn(self):
        pass
 
#Following are Concrete classes i.e Car, Train, Ship
class Car(AutoMobile): 
    # overriding abstract method
    def RunOn(self):
        print("I run on Road.")
 
class Train(AutoMobile): 
    # overriding abstract method
    def RunOn(self):
        print("I run on Rails.")

class Ship(AutoMobile): 
    # overriding abstract method
    def RunOn(self):
        print("I run on Water.")
 
# Driver code
C = Car()
C.RunOn()
 
T = Train()
T.RunOn()

S = Ship()
S.RunOn()

Coming back to the OCP design principle, let’s see, How the class structure is going to violate the Open-Closed Principle?

Consider the Movie and MovieBrowser class below:

class Movie:
    #Setting up attributes
    def __init__(self, name, genre):
        self.name = name
        self.genre = genre

class MovieBrowser:
    #Movie filter by Name
    def search_movie_by_name(self, movies, name):
        return [movie for movie in movies if movie.name == name]

    #Movie filter by Genre
    def search_movie_by_genre(self, movies, genre):
        return [movie for movie in movies if movie.genre == genre]

The Movie class is setting up movies attributes and MovieBrowser class has filters on movie name and genre separately. But, What happens If we want to search by name and genre both(multi-criteria filter)? What if we add release year, and then try to filter on release year? We have to write a new function every time in our existing class MovieBrowser.

Consider the code below:

from abc import ABC, abstractmethod

#Abstract base class
class SearchBy(ABC):
    @abstractmethod
    def is_matched(self, movie):
        pass

#Inheriting SearchBy and Filter on Genre      
class SearchByGenre(SearchBy):
    def __init__(self, genre):
        self.genre = genre
    def is_matched(self, movie):
        return movie.genre == self.genre
 
#Inheriting SearchBy and Filter on Name   
class SearchByName(SearchBy):
    def __init__(self, name):
        self.name = name
    def is_matched(self, movie):
        return movie.name == self.name
    
class MovieBrowser:
    def browse(self, movies, searchby):
        return [movie for movie in movies if searchby.is_matched(movie)]

Instead of clustering all filters in one class, we have defined one common interface and then defined separate subclasses for each specification that inherit the abstract method from the base class. This allows us to extend the searches with another class when we want (e.g. by release date).

But what about multi-criteria search(e.g name and release date)?

from abc import ABC, abstractmethod

#Abstract base class
class SearchBy:
    @abstractmethod
    def is_matched(self, movie):
        pass
    
    #Overloaded and operator
    def __and__(self, other):
        return AndSearchBy(self, other)

#Added AndSearchBy method without altering existing classes
class AndSearchBy(SearchBy):
    def __init__(self, name, release_date):
        self.name = name
        self.release_date = release_date
    def is_matched(self, movie):
        return self.name.is_matched(movie) and self.release_date.is_matched(movie)

The goal of the Open-Closed design principle is to minimize the changes in existing, tested code to prevent bugs and having to test everything all over again. If this principle is not followed, the result could be a long list of changes in depending classes and unnecessary hours of testing. OCP promotes the creation of generalised entities.

Liskov Substitution Principle(LSP)

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

Liskov Substitution Principle simply means, to structure the superclass in such a way that the working of its instance should not be changed, if that instance is replaced(substituted) by the instance of the subclass of the same base class.

To make the entities follow the LSP principle: The subclass must return only a subset of the return values of the base class, but it can accept all the inputs that the superclass does.

Consider the Car and PetrolCar classes below:

#Super Class
class Car():
    def __init__(self, type):
        self.type = type
        self.properties = {} #Dictionary
    
    def set_properties(self, properties):
        self.properties = properties 
 
#Sub Class   
class PetrolCar(Car):
    def __init__(self, type):
        self.type = type
        self.properties = [] #List
    
    def set_properties(self, properties):
        self.properties = properties

#Driver Code
car = Car("Suv")
car.set_properties({"Color": "Yellow", "Gear":"Manual", "Capacity":6})
print(car.properties)

petrol_car = PetrolCar("Sedan")
petrol_car.set_properties(["White", "Auto", 4])
print(petrol_car.properties)   

Clearly, super-class has properties attribute as dictionary data type, while sub-class is having the same attribute as list data type. Till now there is no problem. But let’s say that there is a requirement to find all White coloured cars.

Since both, super and sub-class, have different data types (implementations) we can’t apply any common procedure. That is where violating Liskov Substitution Principle starts causing trouble.

A better way to implement this would be to introduce setter and getter methods in the Superclass Car.
For example –

class Car():
    def __init__(self, type):
        self.type = type
        self.properties = {}
    
    #Setting properties
    def set_properties(self, color, gear, capacity):
        self.properties = {"Color":color, "Gear":gear, "Capacity":capacity}
    
    #Getting properties
    def get_properties(self):
        return self.properties

class PetrolCar(Car):
    def __init__(self, type):
        super().__init__(type)

#Function finding number of White cars
def Find_white_car(cars):
    Wcount = 0
    for car in cars:
        if car.get_properties()['Color'] == "White":
            Wcount += 1
    return Wcount

#Instance of Super class
car = Car("Suv")
car.set_properties("Yellow", "Manual", 6)
print(car.properties)

#Instance of Sub class
petrol_car = PetrolCar("Sedan")
petrol_car.set_properties("White", "Auto", 4)
print(petrol_car.properties)   

print(Find_white_car([car, petrol_car]))

Clearly, objects of super and subclass have exactly similar implementation. LSP asks us to define the interface of a subclass, the same as the interface of the superclass.

Interface Segregation Principle(ISP)

Clients should not be forced to depend upon interfaces that they do not use.

The main idea behind Interface Segregation Principle is that it’s better to have a lot of smaller interfaces than a few bigger ones. ISP suggests interfaces should only have methods that make sense for each and then have subclasses inherit from them.

Let’s consider we are designing communication devices. That can make calls, send SMS and browse the internet. Let’s create an interface for it and add the abstract methods –

#Abstract Class
class CommunicationDevice():
    @abstractmethod
    def make_calls():
        pass
    
    @abstractmethod
    def send_sms():
        pass
     
    @abstractmethod
    def browse_internet():
        pass

Considering two types of communicating devices Smart Phones and Landline Phones, let’s create a sub-class for them.

class SmartPhone(CommmunicationDevices):
    def make_call():
       pass

    def send_sms():
        pass

    def browse_internet():
        pass

class LandlinePhone(CommunicationDevices):
    def make_call():
        pass

    def send_sms():
        print("Not Supported")

    def make_call():
        print("Not Supported")

Clearly, LandlinePhone inherited the properties that it doesn’t support and explicitly error has to be raised. Generally, If a base class have too many methods, possibly not all of the subclasses are going to need them. But due to inheritance, instances will be able to call these methods on all the subclasses. This means a lot of interfaces that are unused, unneeded and will result in bugs when they get accidentally called.

This can be corrected by following the Interface Segregation Principle as below:

class CallingDevice():
    @abstractmethod
    def make_call():
        pass

class MessagingDevice():
    @abstractmethod
    def send_sms():
        pass

class BrowsingDevice():
    @abstractmethod
    def browse_internet():
        pass

#Inherihting only the supported features
class SmartPhone(CallingDevice, MessagingDevice, BrowsingDevice):
    pass

class LandlinePhones(CallingDevice):
    pass

Dependency Inversion Principle(DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions

DIP is to make sure High-level modules, should be unaffected by changes in low-level modules. To ensure this property modules should be connected through a common abstraction.

Dependency Inversion Principle

What are high level and low-level modules?

Low-level classes are the simple workers that perform actions, while high-level classes are the management class that orchestrate the low-level classes.

OR, High-level modules contain the important policy decisions and business models of an application – The identity of the application. Low-level modules contain detailed implementations of individual mechanisms needed to realize the policy.

Coming back to DIP, What happens if violate the Dependency Inversion Principle?

If any software has well-defined connected interfaces, changing the internal implementation of one class will break the code. Because a class interacting with another class will not have knowledge of its inner workings, leading to redefining the entire dependent interface again.

An example would be changing the type of database you use (SQL or NoSQL) or changing the data structure you store your data in (dictionary or list).

The design principle does not just change the direction of the dependency. It suggests decoupling the dependency between the high-level and low-level modules by introducing an abstraction between them.

If software designing follows the Open-Closed Principle and Liskov Substitution Principle, then it will be implicitly aligned to confirm the Dependency Inversion Principle.