
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 Factory Pattern
- The Observer Pattern
- The Decorator Pattern
- The Command Pattern
- The Adapter Pattern
- The Facade Pattern
- The Template Method Pattern
- The Iterator Pattern
- Conclusion
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.