In the realm of software development, design patterns serve as proven solutions to recurring design problems. They encapsulate best practices, offering a blueprint for structuring code in a reusable, maintainable, and scalable manner. By embracing design patterns, Python developers can streamline the development process, enhance code quality, and foster codebase longevity.
Design patterns are reusable solutions to commonly occurring problems in software design. They provide a structured approach to solving design problems and promote code reusability, maintainability, and scalability.
The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Usage
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # Output: True
Singleton
class overrides the __new__
method, which is responsible for creating instances of the class._instance
to hold the singleton instance.Singleton()
is called, it first checks if _instance
is None
. If it is, a new instance of the class is created using super().__new__(cls)
and assigned to _instance
. Otherwise, it returns the existing instance.Singleton()
return the same instance, ensuring that only one instance of the class exists.The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate.
from abc import ABC, abstractmethod
class Product(ABC):
@abstractmethod
def operation(self):
pass
class ConcreteProductA(Product):
def operation(self):
return "Product A operation"
class ConcreteProductB(Product):
def operation(self):
return "Product B operation"
class Creator(ABC):
@abstractmethod
def factory_method(self):
pass
def operation(self):
product = self.factory_method()
return f"Creator: {product.operation()}"
class ConcreteCreatorA(Creator):
def factory_method(self):
return ConcreteProductA()
class ConcreteCreatorB(Creator):
def factory_method(self):
return ConcreteProductB()
# Usage
creator_a = ConcreteCreatorA()
creator_b = ConcreteCreatorB()
print(creator_a.operation()) # Output: Creator: Product A operation
print(creator_b.operation()) # Output: Creator: Product B operation
Product
interface defines the operations that concrete products must implement.ConcreteProductA
and ConcreteProductB
) implement the operation
method defined by the Product
interface.Creator
class declares the factory method factory_method
, which returns a product object. It also provides a common interface for all concrete creators.ConcreteCreatorA
and ConcreteCreatorB
) implement the factory_method to create specific instances of products.operation()
is called on a creator object, it internally calls factory_method()
to create a product instance and then performs operations on that product instance.The Adapter pattern allows objects with incompatible interfaces to collaborate.
class Target:
def request(self):
return "Target: The default target's behavior."
class Adaptee:
def specific_request(self):
return ".eetpadA eht fo roivaheb laicepS"
class Adapter(Target):
def __init__(self, adaptee):
self.adaptee = adaptee
def request(self):
return f"Adapter: (TRANSLATED) {self.adaptee.specific_request()[::-1]}"
# Usage
adaptee = Adaptee()
adapter = Adapter(adaptee)
print(adapter.request()) # Output: Adapter: (TRANSLATED) Special behavior of the Adaptee.
Target
class defines the interface expected by the client.Adaptee
class has a different interface that needs to be adapted.Adapter
class implements the Target
interface and holds an instance of Adaptee
.request()
is called on an Adapter
object, it internally calls specific_request()
on the Adaptee
object and adapts the result to match the Target
interface.The Decorator pattern allows behavior to be added to individual objects, dynamically.
class Component:
def operation(self):
pass
class ConcreteComponent(Component):
def operation(self):
return "ConcreteComponent: operation"
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: {super().operation()}"
class ConcreteDecoratorB(Decorator):
def operation(self):
return f"ConcreteDecoratorB: {super().operation()}"
# Usage
component = ConcreteComponent()
decorator_a = ConcreteDecoratorA(component)
decorator_b = ConcreteDecoratorB(component)
print(component.operation()) # Output: ConcreteComponent: operation
print(decorator_a.operation()) # Output: ConcreteDecoratorA: ConcreteComponent: operation
print(decorator_b.operation()) # Output: ConcreteDecoratorB: ConcreteComponent: operation
Component
class defines the interface for objects that can have responsibilities added to them dynamically.ConcreteComponent
class implements the Component
interface.Decorator
class also implements the Component
interface and holds a reference to a Component
object.ConcreteDecoratorA
and ConcreteDecoratorB
) add additional behavior to the component by overriding its operation
method and calling the parent’s operation
method.The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
class Subject:
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()
class ConcreteSubject(Subject):
def __init__(self):
super().__init__()
self._state = None
@property
def state(self):
return self._state
@state.setter
def state(self, state):
self._state = state
self.notify()
class Observer:
def update(self):
pass
class ConcreteObserverA(Observer):
def update(self):
print("ConcreteObserverA: Reacted to the state change")
class ConcreteObserverB(Observer):
def update(self):
print("ConcreteObserverB: Reacted to the state change")
# Usage
subject = ConcreteSubject()
observer_a = ConcreteObserverA()
observer_b = ConcreteObserverB()
subject.attach(observer_a)
subject.attach(observer_b)
subject.state = 1
# Output: ConcreteObserverA: Reacted to the state change
# Output: ConcreteObserverB: Reacted to the state change
Subject
class represents the object being observed. It maintains a list of its dependents (observers) and notifies them of any changes in its state.ConcreteSubject
class extends Subject
and adds the specific behavior of interest. It notifies its observers whenever its state changes.Observer
class defines the interface for objects that should be notified of changes in the subject’s state.ConcreteObserverA
and ConcreteObserverB
) implement the Observer
interface and define specific reactions to state changes.In the above topic, we've explored various design patterns in Python, covering creational, structural, and behavioral patterns. Design patterns provide reusable solutions to common software design problems, promoting code reusability, maintainability, and scalability. By mastering these patterns, Python developers can write cleaner, more expressive, and modular code, leading to more robust and maintainable software systems. Happy coding! ❤️