🚀AdvancedAdvanced Patterns
Design Patterns in Python
Pythonic implementations of Singleton, Factory, Observer, Strategy, and Command patterns.
Overview
Design patterns are reusable solutions to recurring software design problems. The original "Gang of Four" patterns were written for C++/Java, but Python's first-class functions, decorators, and dynamic nature change how these patterns look and which ones are even needed.
Some patterns that require whole classes in Java become single functions or closures in Python. Others (like Singleton) have multiple idiomatic Python implementations. Understanding both the intent of each pattern and the Pythonic way to implement it makes you a more effective designer.
This topic covers five high-value patterns:
- **Singleton**: only one instance ever exists
- **Factory Method / Abstract Factory**: decouple object creation from use
- **Observer**: event-driven publish/subscribe
- **Strategy**: swap algorithms at runtime
- **Command**: encapsulate an action for queuing, undo, or logging
Key Points
- •Singleton: use a metaclass, module-level variable, or `__new__` override; modules are themselves singletons
- •Factory Method: a class or function that returns an object; lets subclasses change the concrete type
- •Abstract Factory: returns families of related objects without specifying concrete classes
- •Observer: subjects maintain a list of observers and call `update()` on events
- •Strategy: pass a function or callable as a parameter instead of hard-coding an algorithm
- •Command: an object that encapsulates a callable + parameters; enables undo/redo stacks
- •Python's first-class functions often replace the Strategy and Command patterns with simpler code
Examples
Singleton via metaclass
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class AppConfig(metaclass=SingletonMeta):
def __init__(self, env="production"):
self.env = env
a = AppConfig("dev")
b = AppConfig("prod")
print(a is b) # True
print(a.env) # dev (first call wins)Observer / Event System
class EventEmitter:
def __init__(self):
self._listeners: dict = {}
def on(self, event, callback):
self._listeners.setdefault(event, []).append(callback)
return self
def emit(self, event, *args, **kwargs):
for cb in self._listeners.get(event, []):
cb(*args, **kwargs)
class Button(EventEmitter):
def click(self):
self.emit("click", self)
btn = Button()
btn.on("click", lambda b: print("Handler 1: clicked"))
btn.on("click", lambda b: print("Handler 2: clicked"))
btn.click()Strategy pattern Pythonic (functions as strategies)
def bubble_sort(data):
d = data[:]
for i in range(len(d)):
for j in range(len(d)-i-1):
if d[j] > d[j+1]: d[j], d[j+1] = d[j+1], d[j]
return d
def reverse_sort(data):
return sorted(data, reverse=True)
class Sorter:
def __init__(self, strategy=sorted):
self.strategy = strategy
def sort(self, data):
return self.strategy(data)
data = [3, 1, 4, 1, 5, 9, 2, 6]
print(Sorter().sort(data)) # default: sorted
print(Sorter(reverse_sort).sort(data)) # custom strategy
print(Sorter(bubble_sort).sort(data)) # another strategyCommand pattern undo/redo stack
class TextEditor:
def __init__(self):
self.text = ""
self._history = []
def execute(self, command):
self._history.append(command)
command.execute(self)
def undo(self):
if self._history:
self._history.pop().undo(self)
class InsertCommand:
def __init__(self, text):
self.text = text
def execute(self, editor):
editor.text += self.text
def undo(self, editor):
editor.text = editor.text[:-len(self.text)]
ed = TextEditor()
ed.execute(InsertCommand("Hello"))
ed.execute(InsertCommand(" World"))
print(ed.text) # Hello World
ed.undo()
print(ed.text) # HelloCommon Mistakes
- Over-engineering: using a full pattern when a plain function or closure suffices (especially Strategy and Command in Python)
- Singleton without thread safety double-checked locking not needed in Python (GIL helps), but use `threading.Lock` if safe lazy-init matters
- Observer memory leaks listeners that hold references to objects prevent garbage collection; use `weakref` for long-lived subjects
Note
Before implementing a pattern, ask "what problem am I solving?" Python's functions, closures, and built-ins often make classic OO patterns unnecessary. A Strategy is frequently just a function argument; a Command is often a `functools.partial`.
Exercises
Write Python code to solve each exercise. Run it in your browser and check your output.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4