Overview
References
python-patterns python-parameterized-design-patterns
Overview & examples
Design patterns are common solutions to recurring problems in software design. They provide a standard terminology and are specific to particular scenarios. Here’s an overview of some key design patterns implemented in Python:
1. Singleton Pattern
The Singleton ensures a class has only one instance and provides a global point of access to it. This is useful for scenarios like database connections or configurations where multiple instances would cause issues.
Python Example:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
# Usage
singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 == singleton2) # Output: True
2. Factory Pattern
The Factory pattern provides an interface for creating objects, but allows subclasses to alter the type of objects that will be created. This decouples object creation from its implementation.
Python Example:
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof"
class Cat(Animal):
def speak(self):
return "Meow"
class AnimalFactory:
@staticmethod
def get_animal(animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
return None
# Usage
animal = AnimalFactory.get_animal("dog")
print(animal.speak()) # Output: Woof
3. Observer Pattern
The Observer pattern defines an one-to-many relationship between objects, where a change in one object leads to an update in others. It is often used in event handling systems.
Python Example:
class Subject:
def __init__(self):
self._observers = []
def register(self, observer):
self._observers.append(observer)
def notify_all(self, message):
for observer in self._observers:
observer.update(message)
class Observer:
def update(self, message):
pass
class ConcreteObserver(Observer):
def update(self, message):
print(f"Observer received message: {message}")
# Usage
subject = Subject()
observer1 = ConcreteObserver()
observer2 = ConcreteObserver()
subject.register(observer1)
subject.register(observer2)
subject.notify_all("Pattern design is fun!") # Both observers receive the message
4. Decorator Pattern
The Decorator pattern adds new functionality to an existing object dynamically. This is especially useful for adhering to the Open-Closed Principle (open for extension, closed for modification).
Python Example:
def make_bold(func):
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def make_italic(func):
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@make_bold
@make_italic
def greet():
return "Hello"
# Usage
print(greet()) # Output: <b><i>Hello</i></b>
5. Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
Python Example:
class Strategy:
def execute(self, data):
pass
class ConcreteStrategyA(Strategy):
def execute(self, data):
return sorted(data)
class ConcreteStrategyB(Strategy):
def execute(self, data):
return sorted(data, reverse=True)
class Context:
def __init__(self, strategy: Strategy):
self.strategy = strategy
def set_strategy(self, strategy: Strategy):
self.strategy = strategy
def execute_strategy(self, data):
return self.strategy.execute(data)
# Usage
data = [5, 3, 8, 1]
context = Context(ConcreteStrategyA())
print(context.execute_strategy(data)) # Output: [1, 3, 5, 8]
context.set_strategy(ConcreteStrategyB())
print(context.execute_strategy(data)) # Output: [8, 5, 3, 1]
6. Adapter Pattern
The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.
Python Example:
class EuropeanSocket:
def voltage(self):
return 230
def live(self):
return 1
def neutral(self):
return -1
class USASocket:
def voltage(self):
return 120
def live(self):
return 1
def neutral(self):
return 0
class SocketAdapter:
def __init__(self, usa_socket):
self.usa_socket = usa_socket
def voltage(self):
return self.usa_socket.voltage()
def live(self):
return self.usa_socket.live()
def neutral(self):
return self.usa_socket.neutral()
# Usage
usa_socket = USASocket()
adapter = SocketAdapter(usa_socket)
print(f"Adapter voltage: {adapter.voltage()}V") # Output: Adapter voltage: 120V
7. Command Pattern
The Command pattern turns a request into a stand-alone object that contains all information about the request. This is useful for undo functionality, queues of operations, etc.
Python Example:
class Command:
def execute(self):
pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.off()
class Light:
def on(self):
print("Light is ON")
def off(self):
print("Light is OFF")
class RemoteControl:
def __init__(self, command: Command):
self.command = command
def press(self):
self.command.execute()
# Usage
light = Light()
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
remote = RemoteControl(light_on)
remote.press() # Output: Light is ON
remote = RemoteControl(light_off)
remote.press() # Output: Light is OFF
These patterns are widely used to solve design problems and make systems more maintainable, extensible, and scalable. They encapsulate best practices that help developers tackle common software design challenges.