Creational Patterns
Creational patterns deal with object creation mechanisms, providing flexibility in what gets created, how it’s created, and when.
classDiagram
class Creator {
<<abstract>>
+factory_method() Product
}
class ConcreteCreatorA {
+factory_method() Product
}
class ConcreteCreatorB {
+factory_method() Product
}
class Product {
<<interface>>
+operation()
}
class ConcreteProductA {
+operation()
}
class ConcreteProductB {
+operation()
}
Creator <|-- ConcreteCreatorA
Creator <|-- ConcreteCreatorB
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
ConcreteCreatorA ..> ConcreteProductA
ConcreteCreatorB ..> ConcreteProductB
Singleton
Intent: Ensure a class has only one instance and provide a global point of access to it.
When to use: When exactly one object is needed to coordinate actions across the system (e.g., config manager, connection pool).
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Factory Method
Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate.
When to use: When a class can’t anticipate the type of objects it needs to create, or wants subclasses to specify them.
class Notification:
def send(self, msg): raise NotImplementedError
class Email(Notification):
def send(self, msg): print(f"Email: {msg}")
class SMS(Notification):
def send(self, msg): print(f"SMS: {msg}")
def create_notification(channel):
return {"email": Email, "sms": SMS}[channel]()
Abstract Factory
Intent: Provide an interface for creating families of related objects without specifying their concrete classes.
When to use: When the system needs to be independent of how its products are created, and products come in families (e.g., UI themes).
class DarkTheme:
def button(self): return DarkButton()
def checkbox(self): return DarkCheckbox()
class LightTheme:
def button(self): return LightButton()
def checkbox(self): return LightCheckbox()
def build_ui(factory):
return factory.button(), factory.checkbox()
Builder
Intent: Separate the construction of a complex object from its representation so the same process can create different representations.
When to use: When an object requires many optional parameters or multi-step construction.
class QueryBuilder:
def __init__(self):
self._parts = {"select": "*", "where": None, "limit": None}
def select(self, cols): self._parts["select"] = cols; return self
def where(self, cond): self._parts["where"] = cond; return self
def limit(self, n): self._parts["limit"] = n; return self
def build(self): return self._parts.copy()
query = QueryBuilder().select("name").where("age > 21").limit(10).build()
Prototype
Intent: Create new objects by cloning an existing instance rather than constructing from scratch.
When to use: When object creation is expensive and similar objects already exist (e.g., game entities, document templates).
import copy
class Prototype:
def __init__(self, config):
self.config = config
def clone(self):
return copy.deepcopy(self)
template = Prototype({"color": "red", "size": 10})
obj = template.clone()
Structural Patterns
Structural patterns deal with object composition — how classes and objects are assembled to form larger structures.
classDiagram
class Component {
<<interface>>
+operation()
}
class ConcreteComponent {
+operation()
}
class Decorator {
-component: Component
+operation()
}
class ConcreteDecoratorA {
+operation()
}
class ConcreteDecoratorB {
+operation()
}
Component <|.. ConcreteComponent
Component <|.. Decorator
Decorator <|-- ConcreteDecoratorA
Decorator <|-- ConcreteDecoratorB
Decorator o-- Component
Adapter
Intent: Convert the interface of a class into another interface that clients expect.
When to use: When you want to use an existing class but its interface doesn’t match what you need (e.g., wrapping a third-party library).
class LegacyPrinter:
def print_old(self, text): print(f"[OLD] {text}")
class PrinterAdapter:
def __init__(self, legacy):
self._legacy = legacy
def print(self, text):
self._legacy.print_old(text)
Decorator
Intent: Attach additional responsibilities to an object dynamically, providing a flexible alternative to subclassing.
When to use: When you need to add behavior to objects without modifying their class (e.g., logging, caching, auth).
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def process(data):
return data.upper()
Facade
Intent: Provide a simplified interface to a complex subsystem.
When to use: When you want to hide complexity behind a single, easy-to-use entry point.
class OrderFacade:
def __init__(self):
self.inventory = InventoryService()
self.payment = PaymentService()
self.shipping = ShippingService()
def place_order(self, item, card, address):
self.inventory.reserve(item)
self.payment.charge(card, item.price)
self.shipping.ship(item, address)
Proxy
Intent: Provide a surrogate or placeholder to control access to another object.
When to use: When you need lazy initialization, access control, logging, or caching around an object.
class CachedAPI:
def __init__(self, api):
self._api = api
self._cache = {}
def fetch(self, url):
if url not in self._cache:
self._cache[url] = self._api.fetch(url)
return self._cache[url]
Composite
Intent: Compose objects into tree structures to represent part-whole hierarchies, treating individual objects and compositions uniformly.
When to use: When you need tree-like structures where leaves and containers share the same interface (e.g., file systems, UI components).
classDiagram
class FileSystemItem {
<<interface>>
+size() int
}
class File {
-_size: int
+size() int
}
class Directory {
-children: list
+add(item)
+size() int
}
FileSystemItem <|.. File
FileSystemItem <|.. Directory
Directory o-- FileSystemItem
class File:
def __init__(self, size): self._size = size
def size(self): return self._size
class Directory:
def __init__(self): self.children = []
def add(self, item): self.children.append(item)
def size(self): return sum(c.size() for c in self.children)
Bridge
Intent: Decouple an abstraction from its implementation so that the two can vary independently.
When to use: When you want to avoid a cartesian product of classes from combining multiple dimensions of variation (e.g., shapes x renderers).
class CircleShape:
def __init__(self, renderer):
self.renderer = renderer
def draw(self):
self.renderer.render_circle(self)
class SVGRenderer:
def render_circle(self, c): print("SVG circle")
class CanvasRenderer:
def render_circle(self, c): print("Canvas circle")
Behavioral Patterns
Behavioral patterns deal with communication between objects — how they interact and distribute responsibility.
Observer
Intent: Define a one-to-many dependency so that when one object changes state, all dependents are notified automatically.
When to use: When changes to one object require updating others, and you don’t want tight coupling (e.g., event systems, UI data binding).
classDiagram
class Subject {
-_observers: list
+subscribe(observer)
+unsubscribe(observer)
+notify()
}
class Observer {
<<interface>>
+update(data)
}
class ConcreteObserverA {
+update(data)
}
class ConcreteObserverB {
+update(data)
}
Subject --> Observer : notifies
Observer <|.. ConcreteObserverA
Observer <|.. ConcreteObserverB
class EventEmitter:
def __init__(self): self._listeners = {}
def on(self, event, fn):
self._listeners.setdefault(event, []).append(fn)
def emit(self, event, data=None):
for fn in self._listeners.get(event, []):
fn(data)
bus = EventEmitter()
bus.on("save", lambda d: print(f"Saved: {d}"))
bus.emit("save", "doc.txt")
Strategy
Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable.
When to use: When you need to switch between different algorithms at runtime (e.g., sorting, compression, pricing rules).
def quick_sort(data): return sorted(data) # simplified
def bubble_sort(data): return sorted(data) # simplified
class Sorter:
def __init__(self, strategy=quick_sort):
self.strategy = strategy
def sort(self, data):
return self.strategy(data)
Command
Intent: Encapsulate a request as an object, allowing parameterization, queuing, and undo/redo.
When to use: When you need to decouple the sender of a request from the object that executes it (e.g., undo systems, task queues, macros).
class Command:
def __init__(self, receiver, action, *args):
self.execute = lambda: getattr(receiver, action)(*args)
class History:
def __init__(self): self._cmds = []
def run(self, cmd):
cmd.execute()
self._cmds.append(cmd)
State
Intent: Allow an object to alter its behavior when its internal state changes — the object appears to change its class.
When to use: When an object’s behavior depends on its state and it must change behavior at runtime (e.g., order statuses, UI modes).
class DraftState:
def publish(self, doc): doc.state = ReviewState()
class ReviewState:
def publish(self, doc): doc.state = PublishedState()
class PublishedState:
def publish(self, doc): print("Already published")
class Document:
def __init__(self): self.state = DraftState()
def publish(self): self.state.publish(self)
Iterator
Intent: Provide a way to access elements of a collection sequentially without exposing its underlying representation.
When to use: When you need a uniform way to traverse different data structures.
class RangeIterator:
def __init__(self, start, end):
self._current, self._end = start, end
def __iter__(self): return self
def __next__(self):
if self._current >= self._end: raise StopIteration
val = self._current
self._current += 1
return val
Template Method
Intent: Define the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the structure.
When to use: When multiple classes share the same algorithm structure but differ in specific steps (e.g., data pipelines, report generators).
class DataPipeline:
def run(self):
data = self.extract()
data = self.transform(data)
self.load(data)
def extract(self): raise NotImplementedError
def transform(self, data): return data
def load(self, data): raise NotImplementedError
Chain of Responsibility
Intent: Pass a request along a chain of handlers, where each handler decides either to process it or to pass it along.
When to use: When more than one object may handle a request and the handler isn’t known a priori (e.g., middleware, logging levels, approval chains).
class Handler:
def __init__(self, next_handler=None):
self._next = next_handler
def handle(self, request):
if self.can_handle(request): return self.process(request)
if self._next: return self._next.handle(request)
def can_handle(self, req): return False
def process(self, req): pass
Mediator
Intent: Define an object that encapsulates how a set of objects interact, promoting loose coupling.
When to use: When a set of objects communicate in complex ways and you want to centralize the interaction logic (e.g., chat rooms, air traffic control, form validation).
class ChatRoom:
def __init__(self): self._users = []
def join(self, user):
self._users.append(user)
user.room = self
def send(self, msg, sender):
for u in self._users:
if u != sender: u.receive(msg)
Concurrency Patterns
Producer-Consumer
Intent: Decouple data production from data consumption using a shared buffer.
When to use: When producers and consumers operate at different speeds and you need to balance throughput. Common in message queues, data pipelines, and event processing. A thread-safe queue sits between producers (which add work) and consumers (which process it), allowing both sides to operate independently.
Thread Pool
Intent: Maintain a pool of reusable threads to execute tasks, avoiding the overhead of creating and destroying threads repeatedly.
When to use: When you have many short-lived tasks and thread creation cost is significant. The pool pre-creates a fixed number of worker threads that pull tasks from a shared queue. This controls resource usage, prevents thread exhaustion, and provides natural backpressure when the queue is full. Python’s concurrent.futures.ThreadPoolExecutor is a standard implementation.
Architectural Patterns
MVC (Model-View-Controller)
Separates an application into three components: Model (data/logic), View (presentation), and Controller (input handling). The controller updates the model, which notifies the view. Common in web frameworks like Django and Rails.
MVP (Model-View-Presenter)
Similar to MVC but the Presenter acts as a middleman — the view is passive and delegates all logic to the presenter. The view and model never communicate directly. Common in Android development and Windows Forms.
MVVM (Model-View-ViewModel)
The ViewModel exposes data streams that the view binds to declaratively. Changes in the ViewModel automatically reflect in the view via data binding. Common in WPF, SwiftUI, and modern frontend frameworks.
Repository
Abstracts data access behind a collection-like interface. Business logic works with the repository without knowing whether data comes from a database, API, or cache. Simplifies testing and makes data source swapping trivial.
Microservices
Decomposes an application into small, independently deployable services, each owning its own data and communicating via APIs or message queues. Enables independent scaling, deployment, and technology choices at the cost of operational complexity.
Event-Driven Architecture
Components communicate by producing and consuming events through a message broker. Producers don’t know about consumers. Enables high decoupling, scalability, and async processing. Common in systems using Kafka, RabbitMQ, or cloud event buses.
Summary Table
| Pattern | Category | Problem it Solves |
|---|---|---|
| Singleton | Creational | Ensures only one instance of a class exists globally |
| Factory Method | Creational | Delegates object creation to subclasses |
| Abstract Factory | Creational | Creates families of related objects without concrete classes |
| Builder | Creational | Constructs complex objects step-by-step with many options |
| Prototype | Creational | Creates new objects by cloning existing ones |
| Adapter | Structural | Makes incompatible interfaces work together |
| Decorator | Structural | Adds behavior to objects dynamically without subclassing |
| Facade | Structural | Simplifies a complex subsystem behind one interface |
| Proxy | Structural | Controls access to an object (caching, auth, lazy loading) |
| Composite | Structural | Treats individual objects and groups uniformly in tree structures |
| Bridge | Structural | Separates abstraction from implementation to vary independently |
| Observer | Behavioral | Notifies dependents automatically when state changes |
| Strategy | Behavioral | Swaps algorithms at runtime without changing client code |
| Command | Behavioral | Encapsulates requests as objects for queuing, undo, logging |
| State | Behavioral | Changes object behavior when internal state changes |
| Iterator | Behavioral | Traverses collections without exposing internal structure |
| Template Method | Behavioral | Reuses algorithm structure while allowing step customization |
| Chain of Responsibility | Behavioral | Passes requests along a chain until one handles it |
| Mediator | Behavioral | Centralizes complex communication between objects |
| Producer-Consumer | Concurrency | Decouples data production from consumption via a buffer |
| Thread Pool | Concurrency | Reuses threads to efficiently execute many short-lived tasks |
| MVC | Architectural | Separates data, presentation, and input handling |
| MVP | Architectural | Isolates view from logic with a passive view and presenter |
| MVVM | Architectural | Binds view to data declaratively via a view model |
| Repository | Architectural | Abstracts data access behind a clean interface |
| Microservices | Architectural | Decomposes apps into independently deployable services |
| Event-Driven | Architectural | Decouples components via asynchronous event communication |