Design Patterns Mastery
Overview
Design patterns are proven solutions to recurring design problems.
They are vocabulary, not recipes — communicate intent, not implementations.
┌─────────────────┬─────────────────────────────────────────────────────────┐
│ Category │ Patterns │
├─────────────────┼─────────────────────────────────────────────────────────┤
│ Creational │ Factory Method, Abstract Factory, Builder, │
│ │ Singleton, Prototype │
├─────────────────┼─────────────────────────────────────────────────────────┤
│ Structural │ Adapter, Bridge, Composite, Decorator, Facade, Proxy │
├─────────────────┼─────────────────────────────────────────────────────────┤
│ Behavioral │ Strategy, Observer, Command, Chain of Responsibility, │
│ │ Template Method, State, Iterator │
├─────────────────┼─────────────────────────────────────────────────────────┤
│ Modern/FP │ Monad (Result/Either), Pipeline, Specification │
└─────────────────┴─────────────────────────────────────────────────────────┘
Creational Patterns
Factory Method
Intent: Define an interface for creating an object, but let subclasses
decide which class to instantiate.
┌──────────────────────────────────────────────────────────┐
│ Creator │
│ + factoryMethod(): Product ← abstract / overridden │
│ + someOperation() │
│ └── calls factoryMethod() internally │
└──────────────────┬───────────────────────────────────────┘
│ extends
┌────────────┴────────────┐
│ │
┌─────▼──────┐ ┌──────▼─────┐
│ConcreteA │ │ConcreteB │
│Creator │ │Creator │
│+ factory() │ │+ factory() │
│ → ProductA│ │ → ProductB│
└────────────┘ └────────────┘
│ creates │ creates
┌─────▼──────┐ ┌───────▼────┐
│ ProductA │ │ ProductB │
└────────────┘ └────────────┘
When to use:
→ You don't know ahead of time what class to instantiate
→ You want subclasses to control the products they create
→ Framework/library code that others extend
When NOT to use:
→ You have a fixed, known set of products → use direct instantiation
→ Simple cases where a plain function suffices
// Java — Notification Factory
interface Notification {
void send(String message, String recipient);
}
class EmailNotification implements Notification {
public void send(String message, String recipient) {
// send email...
}
}
class SMSNotification implements Notification {
public void send(String message, String recipient) {
// send SMS...
}
}
// Creator — the "factory method" is createNotification()
abstract class NotificationService {
// Factory method — subclasses override this
protected abstract Notification createNotification();
// Business logic uses the factory method
public void notify(String message, String recipient) {
var notification = createNotification(); // ← polymorphic
notification.send(message, recipient);
auditLog(recipient, message); // shared behavior
}
}
class EmailNotificationService extends NotificationService {
@Override
protected Notification createNotification() {
return new EmailNotification();
}
}
# Python — simpler with callables as factories
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send(self, message: str, recipient: str) -> None: ...
class NotificationService(ABC):
@abstractmethod
def _create_notification(self) -> Notification: ...
def notify(self, message: str, recipient: str) -> None:
notification = self._create_notification()
notification.send(message, recipient)
# In Python, often just pass a factory callable instead:
def create_notification_service(notification_factory: Callable[[], Notification]):
def notify(message: str, recipient: str):
notification_factory().send(message, recipient)
return notify
Abstract Factory
Intent: Create families of related objects without specifying their
concrete classes. Ensures products from same family work together.
┌────────────────────────────────────────────────────────────────────────┐
│ AbstractFactory │
│ + createButton(): Button │
│ + createCheckbox(): Checkbox │
└──────────────┬──────────────────────────────────────┬─────────────────┘
│ implements │ implements
┌──────────────▼───────────┐ ┌─────────────▼───────────────┐
│ WindowsFactory │ │ MacFactory │
│ + createButton() │ │ + createButton() │
│ → WindowsButton │ │ → MacButton │
│ + createCheckbox() │ │ + createCheckbox() │
│ → WindowsCheckbox │ │ → MacCheckbox │
└──────────────────────────┘ └─────────────────────────────┘
Key point: WindowsButton and MacButton are related — they look consistent.
You can't accidentally mix WindowsButton with MacCheckbox.
When to use:
→ System must be independent of how its products are created
→ System needs multiple families of products (themes, OS, cloud providers)
→ Products in a family must work together
When NOT to use:
→ You only have one product type → use Factory Method
→ Adding new products to the family is frequent → requires changing AbstractFactory
// Kotlin — Cloud Provider Abstract Factory
interface StorageClient {
fun upload(key: String, data: ByteArray)
fun download(key: String): ByteArray
}
interface QueueClient {
fun publish(queue: String, message: String)
fun consume(queue: String): String?
}
interface CloudFactory {
fun createStorage(): StorageClient
fun createQueue(): QueueClient
}
class AWSFactory : CloudFactory {
override fun createStorage() = S3Client()
override fun createQueue() = SQSClient()
}
class GCPFactory : CloudFactory {
override fun createStorage() = GCSClient()
override fun createQueue() = PubSubClient()
}
// Application code depends only on interfaces
class DataPipeline(private val cloud: CloudFactory) {
private val storage = cloud.createStorage()
private val queue = cloud.createQueue()
fun process() {
val msg = queue.consume("input")
storage.upload("result/$msg", processData(msg!!))
}
}
// Wire up:
val pipeline = DataPipeline(if (env == "aws") AWSFactory() else GCPFactory())
Builder
Intent: Construct complex objects step by step. Separate construction from
representation. Same construction process → different representations.
┌─────────────────────────────────────────────────────────────────────────┐
│ Classic Builder (Director + Builder) │
│ │
│ Director ──── uses ────► Builder (interface) │
│ + construct() + buildPartA() │
│ │ + buildPartB() │
│ │ + getResult(): Product │
│ │ △ │
│ │ ┌──────┴──────┐ │
│ │ ConcreteBuilderA ConcreteBuilderB │
│ └──────────────► (injected) │
│ │
│ Fluent Builder (modern, no Director): │
│ Product.builder() │
│ .field1("value") │
│ .field2(42) │
│ .build() ← validates at build time │
└─────────────────────────────────────────────────────────────────────────┘
When to use:
→ Constructor has many optional parameters (> 4 is a code smell)
→ Object construction involves multiple steps or validation
→ Want to enforce invariants at construction time
When NOT to use:
→ Simple objects with few fields → direct constructor or record/dataclass
→ Immutability not needed → just use setters
// Java — Fluent Builder (Lombok @Builder does this automatically)
public final class HttpRequest {
private final String method;
private final URI uri;
private final Map<String, String> headers;
private final Duration timeout;
private final int maxRetries;
private HttpRequest(Builder builder) {
this.method = Objects.requireNonNull(builder.method, "method required");
this.uri = Objects.requireNonNull(builder.uri, "uri required");
this.headers = Map.copyOf(builder.headers);
this.timeout = builder.timeout;
this.maxRetries = builder.maxRetries;
}
public static Builder builder() { return new Builder(); }
public static final class Builder {
private String method;
private URI uri;
private final Map<String, String> headers = new HashMap<>();
private Duration timeout = Duration.ofSeconds(30);
private int maxRetries = 3;
public Builder method(String method) { this.method = method; return this; }
public Builder uri(URI uri) { this.uri = uri; return this; }
public Builder header(String key, String value) {
this.headers.put(key, value); return this;
}
public Builder timeout(Duration timeout) { this.timeout = timeout; return this; }
public Builder maxRetries(int max) { this.maxRetries = max; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
// Usage:
var request = HttpRequest.builder()
.method("POST")
.uri(URI.create("https://api.example.com/orders"))
.header("Authorization", "Bearer " + token)
.timeout(Duration.ofSeconds(10))
.build();
# Python — dataclass with validation
from dataclasses import dataclass, field
from typing import Optional
@dataclass(frozen=True) # immutable
class HttpRequest:
method: str
url: str
headers: dict = field(default_factory=dict)
timeout_seconds: int = 30
max_retries: int = 3
def __post_init__(self):
if self.method not in ("GET", "POST", "PUT", "PATCH", "DELETE"):
raise ValueError(f"Invalid method: {self.method}")
if self.timeout_seconds <= 0:
raise ValueError("timeout must be positive")
# Python doesn't need a Builder class — dataclasses with defaults
# and __post_init__ validation serve the same purpose.
// Rust — Builder with type-state (compile-time validation)
struct HttpRequest<State> {
method: Option<String>,
url: Option<String>,
_state: std::marker::PhantomData<State>,
}
struct NoUrl;
struct HasUrl;
impl HttpRequest<NoUrl> {
fn new() -> Self { Self { method: None, url: None, _state: PhantomData } }
fn url(self, url: &str) -> HttpRequest<HasUrl> {
HttpRequest { method: self.method, url: Some(url.to_string()), _state: PhantomData }
}
}
impl HttpRequest<HasUrl> {
fn build(self) -> Request {
Request { method: self.method.unwrap_or("GET".into()), url: self.url.unwrap() }
}
}
// Compile error if .url() not called before .build()
let req = HttpRequest::new().url("https://...").build();
Singleton
Intent: Ensure only one instance of a class exists and provide a
global access point to it.
┌────────────────────────────────┐
│ Singleton │
│ - instance: Singleton │ ← static, private
│ - Singleton() │ ← private constructor
│ + getInstance(): Singleton │ ← static, public
│ + businessMethod() │
└────────────────────────────────┘
When it's justified:
→ Logger (one per application)
→ Configuration holder (read at startup, immutable)
→ Connection pool manager (manages shared resources)
→ Registry / service locator
→ Hardware access (one instance controls the device)
When it's an anti-pattern:
→ Encapsulates mutable shared state → global mutable state is evil
→ Makes testing hard (can't inject mocks)
→ Violates Single Responsibility (manages own lifecycle + does work)
→ In tests: state leaks between test cases
Better alternative: Dependency Injection
Register as singleton in DI container → injectable, mockable
Spring @Bean + @Scope("singleton")
Python: single instance at module level, injected via function args
// Java — thread-safe Singleton with double-checked locking
public final class DatabasePool {
private static volatile DatabasePool instance; // volatile essential
private final ConnectionPool pool;
private DatabasePool() {
this.pool = ConnectionPool.create(Config.load());
}
public static DatabasePool getInstance() {
if (instance == null) { // first check (no lock)
synchronized (DatabasePool.class) {
if (instance == null) { // second check (with lock)
instance = new DatabasePool();
}
}
}
return instance;
}
}
// BETTER: Let Spring manage it
@Configuration
public class DatabaseConfig {
@Bean // Spring manages singleton lifecycle
public ConnectionPool connectionPool() {
return ConnectionPool.create(config());
}
}
// Now it's injectable and mockable in tests
// Kotlin — object declaration (language-level singleton)
object DatabasePool {
private val pool = ConnectionPool.create(Config.load())
fun getConnection() = pool.borrow()
fun releaseConnection(conn: Connection) = pool.release(conn)
}
// Usage: DatabasePool.getConnection() — thread-safe, lazy init
// For testability, still prefer injecting an interface
Prototype
Intent: Create new objects by cloning existing ones.
Avoid expensive initialization by copying a configured prototype.
When to use:
→ Object creation is expensive (DB query, API call, heavy computation)
→ Objects differ only slightly (cloning + modifying is cheaper)
→ Building object graphs where structure matters more than class
Java: implement Cloneable (shallow) or copy constructor (deep)
Python: copy.copy() (shallow) / copy.deepcopy() (deep)
Kotlin: data class .copy()
Kotlin example:
data class QueryConfig(
val database: String,
val timeout: Duration = Duration.ofSeconds(30),
val maxRows: Int = 1000,
val readOnly: Boolean = true
)
val baseQuery = QueryConfig(database = "orders_db")
val longQuery = baseQuery.copy(timeout = Duration.ofMinutes(5), maxRows = 50000)
val writeQuery = baseQuery.copy(readOnly = false)
// Original unchanged — value semantics make this safe
Structural Patterns
Adapter
Intent: Convert the interface of a class into another interface
clients expect. Lets incompatible interfaces work together.
Object Adapter (composition — preferred):
┌────────────┐ uses ┌────────────┐ delegates ┌──────────────┐
│ Client │ ──────────── │ Adapter │ ─────────────► │ Adaptee │
│ │ │ │ │ (legacy / │
│ expects: │ │ implements │ │ external) │
│ Target │ │ Target │ │ │
└────────────┘ │ wraps │ └──────────────┘
│ Adaptee │
└────────────┘
When to use:
→ Integrating a third-party library with a different interface
→ Wrapping legacy code behind a modern interface
→ Making classes work together that couldn't due to incompatible interfaces
When NOT to use:
→ Interface is only slightly different — just rename
→ You own both sides — change the interface instead
// Java — Adapt legacy payment processor to modern interface
interface PaymentGateway {
PaymentResult charge(String customerId, Money amount, String currency);
}
// Legacy class we can't modify (third-party SDK)
class LegacyStripeClient {
public StripeCharge createCharge(String tok, int amountCents, String cur) { ... }
}
// Adapter
class StripePaymentAdapter implements PaymentGateway {
private final LegacyStripeClient stripe;
StripePaymentAdapter(LegacyStripeClient stripe) {
this.stripe = stripe;
}
@Override
public PaymentResult charge(String customerId, Money amount, String currency) {
var charge = stripe.createCharge(
customerId,
amount.toMinorUnits(), // convert Money → int cents
currency
);
return PaymentResult.fromStripeCharge(charge); // convert result
}
}
# Python — Adapter using composition
class PaymentGateway(Protocol):
def charge(self, customer_id: str, amount: Decimal, currency: str) -> PaymentResult: ...
class LegacyStripeClient: # can't modify
def create_charge(self, token: str, amount_cents: int, currency: str): ...
class StripePaymentAdapter:
def __init__(self, stripe: LegacyStripeClient):
self._stripe = stripe
def charge(self, customer_id: str, amount: Decimal, currency: str) -> PaymentResult:
charge = self._stripe.create_charge(
token=customer_id,
amount_cents=int(amount * 100),
currency=currency
)
return PaymentResult(success=True, transaction_id=charge.id)
Bridge
Intent: Separate abstraction from implementation. Both can vary independently.
┌──────────────────────────────────────────────────────────────────┐
│ WITHOUT Bridge: class explosion │
│ Shape × Color = classes needed │
│ Circle, Square, Triangle × Red, Green, Blue = 9 classes │
│ │
│ WITH Bridge: composition │
│ Shape (abstraction) has a Color (implementation) │
│ 3 Shapes + 3 Colors = 6 classes │
└──────────────────────────────────────────────────────────────────┘
┌─────────────────────┐ ┌─────────────────────────┐
│ Shape │ has-a ► │ Renderer │
│ + draw() │ │ + renderCircle(r) │
│ # renderer:Renderer│ │ + renderSquare(s) │
└──────────┬──────────┘ └────────────┬────────────┘
△ △
┌──────┴──────┐ ┌──────┴──────┐
Circle Square VectorRenderer RasterRenderer
When to use:
→ Avoid permanent binding between abstraction and implementation
→ Both abstraction and implementation should be extensible via subclasses
→ Hide implementation from clients completely
→ Share implementation among multiple objects
When NOT to use:
→ Simple hierarchy without multiple variation axes
→ Implementation won't change — adds indirection for no benefit
// Kotlin — Message + Delivery Channel Bridge
interface MessageChannel {
fun send(content: String, recipient: String)
}
class EmailChannel : MessageChannel {
override fun send(content: String, recipient: String) { /* send email */ }
}
class SlackChannel : MessageChannel {
override fun send(content: String, recipient: String) { /* post to Slack */ }
}
abstract class Message(protected val channel: MessageChannel) {
abstract fun deliver(recipient: String)
}
class AlertMessage(channel: MessageChannel, private val severity: String) : Message(channel) {
override fun deliver(recipient: String) {
channel.send("[${severity.uppercase()}] Alert triggered", recipient)
}
}
class ReportMessage(channel: MessageChannel, private val period: String) : Message(channel) {
override fun deliver(recipient: String) {
channel.send("Weekly report for $period: ...", recipient)
}
}
// Usage — mix and match independently
val alert = AlertMessage(SlackChannel(), "critical")
val report = ReportMessage(EmailChannel(), "2024-W03")
Composite
Intent: Compose objects into tree structures to represent part-whole
hierarchies. Treat individual objects and compositions uniformly.
┌──────────────────────────────────────────────────────────────┐
│ Component (interface) │
│ + operation() │
│ + add(Component) │
│ + remove(Component) │
└──────────────┬────────────────────────────┬─────────────────┘
△ △
┌──────┴──────┐ ┌────────┴────────┐
│ Leaf │ │ Composite │
│ + operation()│ │ - children[] │
│ │ │ + operation() │
└──────────────┘ │ (iterates) │
└─────────────────┘
Classic example: file system (File = Leaf, Directory = Composite)
When to use:
→ Tree structures: file systems, org charts, UI component trees, DOM
→ Clients should ignore difference between compositions and individual objects
When NOT to use:
→ Flat structure — just use a list
→ Operations differ significantly between leaf and composite
// Java — UI Component tree
interface UIComponent {
void render(int depth);
int getWidth();
}
class Button implements UIComponent {
private final String label;
Button(String label) { this.label = label; }
public void render(int depth) {
System.out.println(" ".repeat(depth) + "[Button: " + label + "]");
}
public int getWidth() { return label.length() + 4; }
}
class Panel implements UIComponent {
private final List<UIComponent> children = new ArrayList<>();
public void add(UIComponent c) { children.add(c); }
public void render(int depth) {
System.out.println(" ".repeat(depth) + "[Panel]");
children.forEach(c -> c.render(depth + 2));
}
public int getWidth() {
return children.stream().mapToInt(UIComponent::getWidth).max().orElse(0);
}
}
// Client treats Panel and Button identically
UIComponent root = new Panel();
root.add(new Button("Submit"));
UIComponent nested = new Panel();
nested.add(new Button("Cancel"));
nested.add(new Button("Back"));
root.add(nested);
root.render(0);
Decorator
Intent: Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for
extending functionality.
┌─────────────────────────────────────────────────────────────────────┐
│ Component (interface) ◄──────────────────────────────────────┐ │
│ + operation(): String │ │
│ △ │ │
│ ┌────┴────┐ ┌─────────────────────────┐ │ │
│ │Concrete │ │ BaseDecorator │ │ │
│ │Component│ │ # wrapped: Component ──┘ │ │
│ └─────────┘ │ + operation() { │ │
│ │ wrapped.operation() │ │
│ │ } │ │
│ └──────────────┬───────────────┘ │
│ △ │
│ ┌───────────┴─────────┐ │
│ LoggingDecorator CachingDecorator│
└─────────────────────────────────────────────────────────────────────┘
When to use:
→ Add behavior to individual objects without affecting others
→ Behavior can be withdrawn
→ Extension by subclassing is impractical (too many combinations)
When NOT to use:
→ Order of decoration matters and is hard to get right
→ Too many thin decorator layers → debugging nightmare
→ Python: prefer Mixin or functools.wraps for simple cases
// Java — HTTP Client decorators
interface HttpClient {
Response execute(Request request);
}
class RealHttpClient implements HttpClient {
public Response execute(Request request) { /* actual HTTP */ }
}
class LoggingHttpClient implements HttpClient {
private final HttpClient wrapped;
LoggingHttpClient(HttpClient wrapped) { this.wrapped = wrapped; }
public Response execute(Request request) {
log.info("HTTP {} {}", request.method(), request.uri());
var start = System.currentTimeMillis();
var response = wrapped.execute(request);
log.info("HTTP {} {} → {} in {}ms",
request.method(), request.uri(),
response.status(), System.currentTimeMillis() - start);
return response;
}
}
class RetryingHttpClient implements HttpClient {
private final HttpClient wrapped;
private final int maxRetries;
public Response execute(Request request) {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
return wrapped.execute(request);
} catch (TransientException e) {
if (attempt == maxRetries) throw e;
Thread.sleep(exponentialBackoff(attempt));
}
}
throw new IllegalStateException("unreachable");
}
}
// Compose decorators:
HttpClient client = new LoggingHttpClient(
new RetryingHttpClient(
new RealHttpClient(), 3));
# Python — Decorator pattern vs Python decorators
# Python's @decorator syntax is the Decorator pattern for functions
import functools
import time
def retry(max_attempts: int = 3, exceptions: tuple = (Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_attempts - 1:
raise
time.sleep(2 ** attempt)
return wrapper
return decorator
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"Calling {func.__name__}")
result = func(*args, **kwargs)
logger.info(f"{func.__name__} returned")
return result
return wrapper
# Stack decorators — order matters (outermost applies last)
@retry(max_attempts=3, exceptions=(ConnectionError,))
@log_calls
def call_external_api(endpoint: str) -> dict:
...
Facade
Intent: Provide a simplified interface to a complex subsystem.
┌────────────────────────────────────────────────────────────────┐
│ Client │
│ │ │
│ └──► Facade ──► SubsystemA ──► SubsystemB ──► SubsystemC │
│ (simple interface) (complex internals hidden) │
└────────────────────────────────────────────────────────────────┘
When to use:
→ Provide a simple interface to a complex subsystem
→ Layer your subsystem — facade defines entry point to each layer
→ Reduce dependencies clients have on internals
Example: OrderFacade that coordinates Inventory, Payment, Fulfillment, Email
// Java — Order placement facade
@Service
public class OrderPlacementFacade {
// Subsystems — complex, with many methods
private final InventoryService inventory;
private final PaymentService payment;
private final FulfillmentService fulfillment;
private final NotificationService notification;
public OrderConfirmation placeOrder(PlaceOrderCommand cmd) {
// Simple interface hides the coordination complexity
inventory.reserve(cmd.items());
var charge = payment.charge(cmd.customerId(), cmd.total());
var shipment = fulfillment.schedule(cmd.items(), cmd.address());
notification.sendConfirmation(cmd.customerId(), shipment.trackingId());
return OrderConfirmation.of(charge.id(), shipment.trackingId());
}
}
// Client calls one method. Doesn't know about 4 subsystems.
Proxy
Intent: Provide a surrogate or placeholder for another object to
control access to it.
┌─────────────────────────────────────────────────────────────┐
│ Proxy types: │
│ │
│ Virtual Proxy → lazy initialization of expensive object │
│ Remote Proxy → local representative for remote object │
│ (gRPC stub, REST client) │
│ Protection Proxy → access control / authorization check │
│ Caching Proxy → cache results of expensive operations │
│ Logging Proxy → log calls before forwarding │
└─────────────────────────────────────────────────────────────┘
Same interface as real subject:
┌────────────┐ ┌──────────────────────────────┐
│ Client │ │ Proxy │
│ │ │ - realSubject: Subject │
│ uses: │ │ + operation() { │
│ Subject │───►│ // pre-processing │
│ interface │ │ realSubject.operation() │
└────────────┘ │ // post-processing │
│ } │
└──────────────────────────────┘
When to use:
→ Lazy initialization (virtual proxy)
→ Access control (protection proxy)
→ Local execution of remote service (remote proxy)
→ Logging and caching without modifying real subject
When NOT to use:
→ Response time matters — every proxy adds a layer
→ Proxy logic becomes more complex than real subject
# Python — Caching Proxy
class ImageLoader:
def load(self, path: str) -> bytes:
# Expensive disk/network operation
with open(path, 'rb') as f:
return f.read()
class CachingImageProxy:
def __init__(self, loader: ImageLoader):
self._loader = loader
self._cache: dict[str, bytes] = {}
def load(self, path: str) -> bytes:
if path not in self._cache:
self._cache[path] = self._loader.load(path)
return self._cache[path]
# Python's @property and __getattr__ enable transparent proxies
Behavioral Patterns
Strategy
Intent: Define a family of algorithms, encapsulate each one, and
make them interchangeable. Strategy lets the algorithm vary
independently from clients that use it.
┌─────────────────────────────────────────────────────────────┐
│ Context ──────────────────────────────► Strategy │
│ - strategy: Strategy + execute() │
│ + setStrategy(s) △ │
│ + executeStrategy() ┌────────┴────────┐ │
│ └── strategy.execute() StrategyA StrategyB│
└─────────────────────────────────────────────────────────────┘
When to use:
→ Multiple related classes differ only in behavior
→ Need different variants of an algorithm
→ Algorithm uses data the client shouldn't know about
→ Class defines many behaviors via conditionals — extract each branch
When NOT to use:
→ Few strategies that never change — just use if/switch
→ Language has first-class functions — just pass a function
// Java — Sorting strategy
interface SortStrategy<T> {
List<T> sort(List<T> items, Comparator<T> comparator);
}
class MergeSortStrategy<T> implements SortStrategy<T> {
public List<T> sort(List<T> items, Comparator<T> comparator) { /* merge sort */ }
}
class QuickSortStrategy<T> implements SortStrategy<T> {
public List<T> sort(List<T> items, Comparator<T> comparator) { /* quicksort */ }
}
class Sorter<T> {
private SortStrategy<T> strategy;
public Sorter(SortStrategy<T> strategy) { this.strategy = strategy; }
public void setStrategy(SortStrategy<T> s) { this.strategy = s; }
public List<T> sort(List<T> items, Comparator<T> cmp) {
return strategy.sort(items, cmp);
}
}
# Python — Strategy with callables (simpler than classes)
from typing import Callable, TypeVar
T = TypeVar('T')
# Strategy IS just a function — no wrapper class needed
def sort_with(
items: list[T],
comparator: Callable[[T, T], int],
strategy: Callable[[list[T], Callable], list[T]] # ← the strategy
) -> list[T]:
return strategy(items, comparator)
# Strategies are plain functions
def merge_sort(items, comparator): ...
def quick_sort(items, comparator): ...
result = sort_with(orders, compare_by_date, merge_sort)
# Swap strategy by passing different function
result = sort_with(orders, compare_by_date, quick_sort)
Observer
Intent: Define one-to-many dependency so that when one object changes
state, all its dependents are notified and updated automatically.
┌──────────────────────────────────────────────────────────────────┐
│ Subject (Observable) Observer │
│ - observers: List + update(event) │
│ + subscribe(Observer) △ │
│ + unsubscribe(Observer) ┌─────────┴──────────┐ │
│ + notify(event) Observer1 Observer2 │
└──────────────────────────────────────────────────────────────────┘
When to use:
→ Object needs to notify unknown number of other objects
→ Change in one object requires changing others; don't know how many
→ Event-driven systems; UI frameworks
When NOT to use:
→ Observers form a chain → unexpected cascades
→ Order of notification matters and is not guaranteed
→ Memory leaks: observers not removed → subject holds reference
// Kotlin — Event bus (simplified Observer)
data class OrderCreatedEvent(val orderId: String, val customerId: String)
interface EventHandler<T> {
fun handle(event: T)
}
class EventBus {
private val handlers = mutableMapOf<Class<*>, MutableList<EventHandler<*>>>()
fun <T : Any> subscribe(eventType: Class<T>, handler: EventHandler<T>) {
handlers.getOrPut(eventType) { mutableListOf() }.add(handler)
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> publish(event: T) {
handlers[event::class.java]?.forEach { handler ->
(handler as EventHandler<T>).handle(event)
}
}
}
// Usage
val bus = EventBus()
bus.subscribe(OrderCreatedEvent::class.java) { event ->
emailService.sendConfirmation(event.customerId)
}
bus.subscribe(OrderCreatedEvent::class.java) { event ->
analyticsService.track("order_created", event.orderId)
}
bus.publish(OrderCreatedEvent("ord_123", "cust_456"))
// Both handlers called
Command
Intent: Encapsulate a request as an object, thereby letting you
parameterize clients with different requests, queue them,
log them, or support undo/redo.
┌────────────────────────────────────────────────────────────────────┐
│ │
│ Invoker ──────────────────► Command (interface) │
│ - command: Command + execute() │
│ + setCommand(c) + undo() │
│ + executeCommand() △ │
│ ┌────────┴──────────┐ │
│ ConcreteCmd1 ConcreteCmd2 │
│ - receiver: R state for undo │
│ + execute() │
│ + undo() │
└────────────────────────────────────────────────────────────────────┘
When to use:
→ Parameterize objects with operations
→ Queue, log, and execute requests at different times
→ Implement undo/redo
→ Transaction-like bundles of operations
When NOT to use:
→ Simple direct calls suffice
→ No need for undo/redo, queuing, or logging
// Java — Command with undo for text editor
interface TextCommand {
void execute();
void undo();
}
class InsertTextCommand implements TextCommand {
private final TextBuffer buffer;
private final int position;
private final String text;
InsertTextCommand(TextBuffer buffer, int position, String text) {
this.buffer = buffer;
this.position = position;
this.text = text;
}
public void execute() { buffer.insert(position, text); }
public void undo() { buffer.delete(position, position + text.length()); }
}
class CommandHistory {
private final Deque<TextCommand> history = new ArrayDeque<>();
private final Deque<TextCommand> undone = new ArrayDeque<>();
public void execute(TextCommand cmd) {
cmd.execute();
history.push(cmd);
undone.clear(); // new command clears redo stack
}
public void undo() {
if (!history.isEmpty()) {
var cmd = history.pop();
cmd.undo();
undone.push(cmd);
}
}
public void redo() {
if (!undone.isEmpty()) {
var cmd = undone.pop();
cmd.execute();
history.push(cmd);
}
}
}
Chain of Responsibility
Intent: Avoid coupling sender of request to receiver. Give multiple
objects a chance to handle the request. Chain the objects and
pass request along chain until handled.
┌──────────────────────────────────────────────────────────────────┐
│ Request ──► Handler1 ──► Handler2 ──► Handler3 ──► (unhandled) │
│ handles? handles? handles? │
│ yes: stop no: pass on yes: stop │
└──────────────────────────────────────────────────────────────────┘
Modern form: middleware pipeline (Express, Spring interceptors, Django middleware)
When to use:
→ More than one object may handle a request, handler determined at runtime
→ Set of handlers should be dynamic
→ Middleware pipelines: auth, logging, rate limiting, caching
When NOT to use:
→ Request must be handled — no guarantee with pure cha
…(truncated)