Sharing is caring 🙂

Design patterns are reusable solutions to common software design problems. They provide a structured approach to solving problems that arise in object-oriented programming. Design patterns are not specific to any programming language, but they can be implemented in different languages with varying levels of complexity. In this tutorial, we will discuss several design patterns and how they can be implemented in Python, a popular object-oriented programming language. By understanding and utilizing design patterns, programmers can write more efficient, maintainable, and flexible code.

The Singleton Pattern

The singleton pattern is a design pattern that ensures a class has only one instance and provides a global access point to that instance. This pattern is useful when only a single instance of a class is required to control the action throughout the execution. For example, a single database connection shared by multiple objects, rather than creating a separate connection for each object. In Python, the singleton pattern can be implemented by creating a class variable and a new method that controls the instantiation of the object. The new method checks if an instance of the class already exists and returns that instance, otherwise it creates a new instance. This ensures that only one instance of the class is ever created, and it can be accessed globally.

Here is an example of how the singleton pattern can be implemented in Python:

class Singleton:
    __instance = None
    def __new__(cls):
        if Singleton.__instance is None:
            Singleton.__instance = object.__new__(cls)
        return Singleton.__instance

s1 = Singleton()
s2 = Singleton()

print(s1 is s2) #True

In this example, the Singleton class has a class variable __instance that keeps track of the single instance of the class. The __new__ method is overridden to check if an instance of the class already exists, if not it creates a new instance and stores it in the __instance variable. Any subsequent calls to the Singleton class will return the same instance stored in __instance, ensuring that only one instance of the class is ever created. In this example, creating two objects s1 and s2 using the Singleton class, and comparing them with is operator , returns True because they are the same instances.

The Factory Pattern

The factory pattern is a design pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created. This pattern is useful when a class can’t anticipate the type of objects it must create, or when a class wants its subclasses to specify the objects it creates. In Python, the factory pattern can be implemented by creating a factory method or an abstract factory class that is responsible for creating objects. The factory method or class takes care of the decision-making process of creating objects and can be overridden by subclasses to change the type of objects that are created.

Here is an example of how the factory pattern can be implemented in Python:

class Dog:
    def __init__(self, name):
        self._name = name

    def speak(self):
        return "Woof!"

class Cat:
    def __init__(self, name):
        self._name = name

    def speak(self):
        return "Meow!"

def get_pet(pet="dog"):
    pets = dict(dog=Dog("Hope"), cat=Cat("Peace"))
    return pets[pet]

d = get_pet("dog")
print(d.speak())

c = get_pet("cat")
print(c.speak())

In this example, the get_pet function acts as a factory method that creates Dog and Cat objects. It takes an optional parameter pet which specifies the type of object to create. If pet is not specified, it defaults to creating a Dog object. The factory method uses a dictionary to map pet types to their corresponding classes, and returns an instance of the appropriate class. In this example, when the get_pet("dog") is called it returns the instance of the Dog class and when get_pet("cat") is called it returns the instance of the Cat class.

The Observer Pattern

The observer pattern is a design pattern that allows an object (the subject) to notify other objects (the observers) of any changes to its state. This pattern is useful when multiple objects need to be notified of changes to another object, and it promotes loose coupling between objects. In Python, the observer pattern can be implemented using the built-in abc module which provides the ABC and abstractmethod decorators for creating abstract base classes (ABCs) and abstract methods.

Here is an example of how the observer pattern can be implemented in Python:

from abc import ABC, abstractmethod

class Subject(ABC):
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def detach(self, observer):
        self._observers.remove(observer)

    def notify(self):
        for observer in self._observers:
            observer.update()

    @abstractmethod
    def get_state(self):
        pass

    @abstractmethod
    def set_state(self, state):
        pass

class ConcreteSubject(Subject):
    def __init__(self):
        super().__init__()
        self._state = None

    def get_state(self):
        return self._state

    def set_state(self, state):
        self._state = state
        self.notify()

class Observer(ABC):
    @abstractmethod
    def update(self):
        pass

class ConcreteObserver(Observer):
    def __init__(self, subject):
        self._subject = subject
        self._subject.attach(self)

    def update(self):
        print(f'Observer: {self} has state {self._subject.get_state()}')

s = ConcreteSubject()
o1 = ConcreteObserver(s)
o2 = ConcreteObserver(s)

s.set_state(0)
s.set_state(1)

In this example, the Subject class defines the interface for notifying observers, the ConcreteSubject class is a specific implementation of the subject that holds a state and notifies the observers when its state changes. The Observer class defines the interface for updating the observer, the ConcreteObserver class is a specific implementation of the observer that holds a reference to the subject and prints the current state of the subject when it is notified. The subject has a list of observers, which it notifies when its state changes. Each observer has a reference to the subject, and when it is notified, it updates its own state based on the subject’s state. In this example, when the state of the subject changes, it notifies the observer and the observer prints the current state of the subject.

The Decorator Pattern

The decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is useful when you want to add new functionality to an existing class without modifying its code. In Python, the decorator pattern can be implemented using decorators, which are special kind of functions that can be applied to other functions or classes to modify their behavior.

Here is an example of how the decorator pattern can be implemented in Python:

class Component:
    def operation(self):
        pass

class ConcreteComponent(Component):
    def operation(self):
        return "ConcreteComponent"

class Decorator(Component):
    def __init__(self, component):
        self._component = component

    def operation(self):
        return self._component.operation()

class ConcreteDecoratorA(Decorator):
    def operation(self):
        return f"ConcreteDecoratorA({self._component.operation()})"

class ConcreteDecoratorB(Decorator):
    def operation(self):
        return f"ConcreteDecoratorB({self._component.operation()})"

# Client code
component = ConcreteComponent()
decoratorA = ConcreteDecoratorA(component)
decoratorB = ConcreteDecoratorB(decoratorA)
print(decoratorB.operation())

In this example, the Component class defines the interface for objects that can have behavior added to them, the ConcreteComponent class is a specific implementation of the component that has some behavior. The Decorator class is an abstract class that implements the Component interface and has a reference to a Component object. The ConcreteDecoratorA and ConcreteDecoratorB classes are specific implementations of the Decorator class that add new behavior to the Component they decorate. The decorator classes are applied to the Component in a nested fashion, with each decorator wrapping the previous one. The operation() method is called on the outermost decorator and it returns the result of the operation() method of the innermost decorator, concatenated with the name of the decorator class. In this example, the ConcreteDecoratorB class is applied to the ConcreteDecoratorA, and the ConcreteDecoratorA is applied to the ConcreteComponent class, so the output of the decoratorB.operation() is ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

The decorator pattern is a powerful tool for adding new functionality to existing classes, and it’s commonly used in Python for example to add logging, caching, or security features to existing code.

The Command Pattern

The command pattern is a design pattern that encapsulates a request or an action as an object, separating the command from the object that invokes it. This pattern is useful when you want to parametrize objects with different requests, delay or queue a request’s execution, and support undo/redo. In Python, the command pattern can be implemented by creating an interface for command objects and classes that implement that interface.

Here is an example of how the command pattern can be implemented in Python:

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

class ConcreteCommand(Command):
    def __init__(self, receiver, value):
        self._receiver = receiver
        self._value = value

    def execute(self):
        self._receiver.action(self._value)

class Receiver:
    def action(self, value):
        print(f"Receiver action with value: {value}")

class Invoker:
    def __init__(self):
        self._commands = []

    def store_command(self, command):
        self._commands.append(command)

    def execute_commands(self):
        for command in self._commands:
            command.execute()

# Client code
receiver = Receiver()
command = ConcreteCommand(receiver, "Hello")
invoker = Invoker()
invoker.store_command(command)
invoker.execute_commands()

In this example, the Command class defines the interface for command objects and the ConcreteCommand class is a specific implementation of the command that holds a reference to the Receiver object and the value it should operate on. The Receiver class defines the action that the command will invoke. The Invoker class holds a list of commands and can execute them. In this example, the client code creates a Receiver object, a ConcreteCommand object that’s associated with the Receiver and a value, and an Invoker object. It stores the command in the invoker, and then it invokes it. The command’s execute method is called which in turn calls the Receiver’s action method with the value passed in the ConcreteCommand’s constructor. The output of the code is Receiver action with value: Hello

The Command pattern is a powerful pattern that allows you to decouple the objects that invoke a request from the objects that handle the request, and it’s commonly used in Python for example to implement undo/redo functionality or to implement a remote control system.

The Adapter Pattern

The adapter pattern is a design pattern that allows objects with incompatible interfaces to work together by wrapping an instance of the adaptee class with a class that conforms to the target interface. This pattern is useful when you want to reuse existing classes that cannot be modified to fit the required interface, and it promotes loose coupling between classes. In Python, the adapter pattern can be implemented by creating a class that wraps an instance of the adaptee class and implements the target interface.

Here is an example of how the adapter pattern can be implemented in Python:

class Adaptee:
    def specific_request(self):
        return "Adaptee's specific request"

class Target:
    def request(self):
        pass

class Adapter(Target):
    def __init__(self, adaptee):
        self._adaptee = adaptee

    def request(self):
        return self._adaptee.specific_request()

# Client code
adaptee = Adaptee()
adapter = Adapter(adaptee)
print(adapter.request())

In this example, the Adaptee class defines a specific interface that the client wants to use, but it’s not compatible with the client’s code. The Target class defines the interface that the client code expects. The Adapter class wraps an instance of the Adaptee class and implements the Target interface, so it can be used by the client code. In this example, the client code creates an instance of the Adaptee class, an instance of the Adapter class and passes the Adaptee instance to the Adapter constructor, then it calls the request() method on the Adapter instance. The Adapter class receives the request, and then it delegates the request to the Adaptee class’s specific_request() method. The output of the code is “Adaptee’s specific request”

The Adapter pattern is a powerful pattern that allows you to reuse existing classes that cannot be modified to fit the required interface, and it’s commonly used in Python for example to integrate third-party libraries or to create a unified interface for different implementations of a service.

The Facade Pattern

The facade pattern is a design pattern that provides a simplified interface to a complex system of classes. It acts as an intermediary between the client and the system, hiding the complexity of the system and exposing only the necessary functionality to the client. This pattern is useful when you want to provide a simple and easy-to-use interface to a complex system of classes, and it promotes loose coupling between classes. In Python, the facade pattern can be implemented by creating a facade class that wraps the complex system of classes and exposes a simplified interface to the client.

Here is an example of how the facade pattern can be implemented in Python:

class Subsystem1:
    def method1(self):
        print("Subsystem1 method1")

class Subsystem2:
    def method2(self):
        print("Subsystem2 method2")

class Facade:
    def __init__(self):
        self._subsystem1 = Subsystem1()
        self._subsystem2 = Subsystem2()

    def method(self):
        self._subsystem1.method1()
        self._subsystem2.method2()

# Client code
facade = Facade()
facade.method()

In this example, the Subsystem1 and Subsystem2 classes define the complex functionality of the system. The Facade class provides a simplified interface to the client, it wraps the instances of the Subsystem1 and Subsystem2 classes and exposes a single method() that invokes the necessary methods of the subsystems. In this example, the client code creates an instance of the Facade class and calls the method() on it. The Facade class receives the request, and then it delegates the request to the appropriate methods of the Subsystem1 and Subsystem2 classes. The output of the code is “Subsystem1 method1” and “Subsystem2 method2”

The Facade pattern is a powerful pattern that allows you to hide the complexity of a system and provide a simplified interface to the client, and it’s commonly used in Python for example to provide a unified interface for different libraries or to simplify the usage of complex APIs.

The Template Method Pattern

The template method pattern is a design pattern that defines the skeleton of an algorithm in a method, and allowing subclasses to fill in the details. It provides a way to define the basic steps of an algorithm, and then allows subclasses to provide their own implementation for one or more of those steps. This pattern is useful when you want to define the skeleton of an algorithm, but you want to allow subclasses to provide their own implementation for some of the steps, and it promotes code reuse. In Python, the template method pattern can be implemented by creating a base class that defines the template method, and then creating subclasses that override one or more of the steps of the algorithm.

Here is an example of how the template method pattern can be implemented in Python:

class AbstractClass:
    def template_method(self):
        self._step1()
        self._step2()

    def _step1(self):
        pass

    def _step2(self):
        pass

class ConcreteClass(AbstractClass):
    def _step1(self):
        print("ConcreteClass step1")

    def _step2(self):
        print("ConcreteClass step2")

# Client code
concrete = ConcreteClass()
concrete.template_method()

In this example, the AbstractClass defines the template method, template_method() which calls the two steps of the algorithm, _step1() and _step2(). These methods are defined as abstract method so it can be implemented by the subclass. The ConcreteClass is a subclass of AbstractClass which provides the implementation for the two steps of the algorithm. The template_method() can be used as is, and it calls the two steps of the algorithm in the correct order. The client code creates an instance of the ConcreteClass and calls the template_method() on it. The ConcreteClass receives the request, and then it executes the two steps of the algorithm by calling the _step1() and _step2() methods. The output of the code is “ConcreteClass step1” and “ConcreteClass step2”

The Template Method pattern is a powerful pattern that allows you to define the skeleton of an algorithm and then allow subclasses to provide their own implementation for some of the steps, and it’s commonly used in Python for example to define the basic structure of a class, but allow subclasses to provide their own implementation for some of the methods.

The Iterator Pattern

The iterator pattern is a design pattern that provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It allows the client to traverse a collection of objects without the need to know the implementation details of the collection. This pattern is useful when you want to provide a consistent way to access and traverse a collection of objects, and it promotes loose coupling between the collection and the client. In Python, the iterator pattern can be implemented by creating an iterator class that implements the __iter__() and __next__() methods and using the iter() and next() built-in functions.

Here is an example of how the iterator pattern can be implemented in Python:

class Iterator:
    def __init__(self, data):
        self._index = 0
        self._data = data

    def __iter__(self):
        return self

    def __next__(self):
        if self._index >= len(self._data):
            raise StopIteration()
        result = self._data[self._index]
        self._index += 1
        return result

class Aggregate:
    def __init__(self):
        self._data = [1, 2, 3, 4, 5]

    def create_iterator(self):
        return Iterator(self._data)

# Client code
aggregate = Aggregate()
iterator = aggregate.create_iterator()
for item in iterator:
    print(item)

In this example, the Iterator class implements the iterator pattern by defining the __iter__() and __next__() methods. The Aggregate class represents the collection of objects and it has a create_iterator() method that returns an instance of the Iterator class. The client code creates an instance of the Aggregate class, then it creates an iterator and uses it to traverse the collection of objects by calling the next() function on it. The Iterator class keeps track of the current position in the collection, and it returns the next item in the collection with the __next__() method. If there are no more items in the collection, it raises a StopIteration exception. In this example, the client code creates an instance of the Aggregate class, then it creates an iterator by calling the create_iterator() method and uses a for loop to traverse the collection of objects by calling the next() function on it. The Iterator class keeps track of the current position in the collection, and it returns the next item in the collection with the __next__() method. The output of the code is 1, 2, 3, 4, 5

The Iterator pattern is a powerful pattern that allows you to provide a consistent way to access and traverse a collection of objects and it’s commonly used in Python for example to traverse lists, sets, dictionaries and other types of collections.

Conclusion

In conclusion, design patterns are a set of well-described solutions to recurring design problems. They provide a common vocabulary and a set of best practices for designing software systems. In this tutorial, we’ve discussed several popular design patterns in the context of Python’s object-oriented programming, including the Singleton, Factory, Observer, Decorator, Command, Adapter, Facade, Template Method and Iterator patterns. Each of these patterns addresses a specific problem in the design of software systems and provides a solution that can be adapted to fit different situations. By understanding these patterns, you’ll be able to make better design decisions and write more maintainable, extensible and reusable code.

Sharing is caring 🙂