🎓 Advanced · Topic 09
Design Patterns in Python
Reusable solutions to common software design problems. Learn the most practical patterns — Singleton, Factory, Observer, Strategy, and more — implemented idiomatically in Python.
⏱ ~65 min
🔴 Advanced
🏛️ Architecture
Pattern Categories
Creational How objects are created — Singleton, Factory, Builder, Prototype
Structural How objects are composed — Decorator, Adapter, Proxy, Facade
Behavioural How objects communicate — Observer, Strategy, Command, Iterator
Factory Pattern
A factory creates objects without exposing instantiation logic. In Python, a simple dict-dispatch factory is idiomatic and avoids lengthy if/elif chains.
factory.py
class JSONSerializer:
def serialize(self, data): return f"JSON:{data}"
class XMLSerializer:
def serialize(self, data): return f"<data>{data}</data>"
class CSVSerializer:
def serialize(self, data): return f"csv,{data}"
# Dict-dispatch factory — easily extensible
_SERIALIZERS = {
"json": JSONSerializer,
"xml": XMLSerializer,
"csv": CSVSerializer,
}
def get_serializer(fmt: str):
try:
return _SERIALIZERS[fmt]()
except KeyError:
raise ValueError(f"Unknown format: {fmt}")
s = get_serializer("json")
print(s.serialize("hello")) # JSON:hello
Observer Pattern
Lets objects subscribe to events. When the subject changes state, all observers are notified automatically — the foundation of event-driven systems.
observer.py
from __future__ import annotations
from typing import Callable
class EventEmitter:
def __init__(self):
self._handlers: dict[str, list[Callable]] = {}
def on(self, event: str, handler: Callable):
self._handlers.setdefault(event, []).append(handler)
def emit(self, event: str, *args, **kwargs):
for h in self._handlers.get(event, []):
h(*args, **kwargs)
bus = EventEmitter()
bus.on("login", lambda u: print(f"Welcome {u}"))
bus.on("login", lambda u: print(f"Logging: {u} logged in"))
bus.emit("login", "Alice")
Strategy Pattern
Define a family of algorithms and make them interchangeable. In Python, functions are first-class, so a strategy is simply a callable — no abstract class required.
strategy.py
from typing import Callable
def sort_by_price(items): return sorted(items, key=lambda x: x["price"])
def sort_by_name(items): return sorted(items, key=lambda x: x["name"])
def sort_by_rating(items): return sorted(items, key=lambda x: -x["rating"])
class ProductListing:
def __init__(self, strategy: Callable):
self.strategy = strategy
def display(self, products):
return self.strategy(products)
listing = ProductListing(sort_by_price)
listing.strategy = sort_by_rating # swap at runtime