Design Patterns Refresher
Gang of Four classics + modern patterns — with examples in Java, Python, and Go
Table of Contents
Overview & Classification
Design patterns are reusable solutions to common software design problems. They aren't code you copy-paste — they're templates for solving recurring structural and behavioral challenges. The "Gang of Four" (GoF) cataloged 23 patterns in 1994; many more have emerged since.
if branches. The cure shouldn't be worse than the disease.
GoF Catalog (23 Patterns)
| Category | Pattern | Purpose (one line) |
|---|---|---|
| Creational | Singleton | Ensure exactly one instance |
| Factory Method | Defer instantiation to subclasses | |
| Abstract Factory | Create families of related objects | |
| Builder | Construct complex objects step-by-step | |
| Prototype | Clone existing objects | |
| Structural | Adapter | Convert one interface to another |
| Bridge | Separate abstraction from implementation | |
| Composite | Tree structures with uniform interface | |
| Decorator | Add behavior dynamically | |
| Facade | Simplified interface to subsystem | |
| Flyweight | Share common state to save memory | |
| Proxy | Control access to another object | |
| Behavioral | Chain of Responsibility | Pass request along handler chain |
| Command | Encapsulate request as object | |
| Iterator | Sequential access without exposing internals | |
| Mediator | Reduce direct dependencies between objects | |
| Memento | Capture/restore object state | |
| Observer | Notify dependents of state changes | |
| State | Behavior changes with internal state | |
| Strategy | Interchangeable algorithms | |
| Template Method | Algorithm skeleton, subclasses fill steps | |
| Visitor | Add operations without modifying classes | |
| Interpreter | Grammar representation and evaluation |
Beyond GoF Modern
Patterns that emerged from microservices, DDD, and cloud-native development:
- Dependency Injection — invert control of object creation
- Repository — abstract data access behind collection-like interface
- Circuit Breaker — prevent cascading failures
- CQRS — separate read/write models
- Event Sourcing — store state as event sequence
- Saga — manage distributed transactions
- Sidecar / Ambassador — infrastructure concerns alongside services
Creational Patterns
Control how objects are created, hiding creation logic and making the system independent of how objects are composed.
Singleton
Intent: Ensure a class has exactly one instance and provide a global access point.
Java — Enum Singleton (recommended)
public enum DatabaseConnection {
INSTANCE;
private final Connection conn;
DatabaseConnection() {
conn = DriverManager.getConnection("jdbc:...");
}
public Connection getConnection() { return conn; }
}
// Usage
DatabaseConnection.INSTANCE.getConnection();
Java — Double-checked locking (legacy)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Python — Module-level singleton
# db.py — the module IS the singleton
_connection = None
def get_connection():
global _connection
if _connection is None:
_connection = create_connection("jdbc:...")
return _connection
Go — sync.Once
var (
instance *Database
once sync.Once
)
func GetDB() *Database {
once.Do(func() {
instance = &Database{conn: connect("...")}
})
return instance
}
- Hidden dependencies — callers don't declare what they need
- Hard to test — can't substitute mock implementations easily
- Thread safety complexity (Java double-checked locking)
Factory Method
Intent: Define an interface for creating objects, but let subclasses decide which class to instantiate.
// Product interface
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String msg) { /* send email */ }
}
class SMSNotification implements Notification {
public void send(String msg) { /* send SMS */ }
}
// Factory method
abstract class NotificationFactory {
abstract Notification createNotification();
public void notify(String msg) {
Notification n = createNotification();
n.send(msg);
}
}
class EmailFactory extends NotificationFactory {
Notification createNotification() { return new EmailNotification(); }
}
Python — Registry pattern (more Pythonic)
class Notification:
_registry: dict[str, type] = {}
def __init_subclass__(cls, type_key: str = "", **kwargs):
super().__init_subclass__(**kwargs)
if type_key:
Notification._registry[type_key] = cls
@classmethod
def create(cls, type_key: str) -> "Notification":
return cls._registry[type_key]()
class EmailNotification(Notification, type_key="email"):
def send(self, msg): ...
class SMSNotification(Notification, type_key="sms"):
def send(self, msg): ...
# Usage
notifier = Notification.create("email")
Abstract Factory
Intent: Create families of related objects without specifying concrete classes.
// Abstract products
interface Button { void render(); }
interface Checkbox { void render(); }
// Concrete products: macOS
class MacButton implements Button { public void render() { /* macOS button */ } }
class MacCheckbox implements Checkbox { public void render() { /* macOS checkbox */ } }
// Concrete products: Windows
class WinButton implements Button { public void render() { /* Windows button */ } }
class WinCheckbox implements Checkbox { public void render() { /* Windows checkbox */ } }
// Abstract factory
interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
class MacFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
class WinFactory implements UIFactory {
public Button createButton() { return new WinButton(); }
public Checkbox createCheckbox() { return new WinCheckbox(); }
}
| Aspect | Factory Method | Abstract Factory |
|---|---|---|
| Creates | One product | Family of products |
| Mechanism | Inheritance (subclass overrides) | Composition (factory object) |
| Use when | One product varies | Multiple related products vary together |
Builder
Intent: Construct complex objects step-by-step, separating construction from representation.
Java — Fluent Builder
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final Duration timeout;
private HttpRequest(Builder b) {
this.url = b.url;
this.method = b.method;
this.headers = Map.copyOf(b.headers);
this.body = b.body;
this.timeout = b.timeout;
}
public static class Builder {
private final String url; // required
private String method = "GET"; // optional with default
private Map<String, String> headers = new HashMap<>();
private String body;
private Duration timeout = Duration.ofSeconds(30);
public Builder(String url) { this.url = url; }
public Builder method(String m) { this.method = m; return this; }
public Builder header(String k, String v) { headers.put(k, v); return this; }
public Builder body(String b) { this.body = b; return this; }
public Builder timeout(Duration t) { this.timeout = t; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
// Usage
var req = new HttpRequest.Builder("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.body("{\"key\": \"value\"}")
.timeout(Duration.ofSeconds(10))
.build();
Go — Functional Options (idiomatic)
type Server struct {
host string
port int
timeout time.Duration
maxConn int
}
type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func WithTimeout(t time.Duration) Option { return func(s *Server) { s.timeout = t } }
func WithMaxConn(n int) Option { return func(s *Server) { s.maxConn = n } }
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // defaults
timeout: 30 * time.Second,
maxConn: 100,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer("localhost",
WithPort(9090),
WithTimeout(10*time.Second),
)
Prototype
Intent: Create new objects by cloning existing ones, avoiding expensive initialization.
public class GameUnit implements Cloneable {
private String type;
private int health;
private List<String> abilities;
@Override
public GameUnit clone() {
try {
GameUnit copy = (GameUnit) super.clone();
copy.abilities = new ArrayList<>(this.abilities); // deep copy
return copy;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
// Better: copy constructor
public GameUnit(GameUnit other) {
this.type = other.type;
this.health = other.health;
this.abilities = new ArrayList<>(other.abilities);
}
Python
import copy
prototype = {"type": "warrior", "health": 100, "abilities": ["slash", "block"]}
unit = copy.deepcopy(prototype) # full independent copy
unit["health"] = 80
Object.clone() does a shallow copy. Prefer copy constructors or a static from() factory method.
Structural Patterns
Deal with object composition — how classes and objects are assembled to form larger structures.
Adapter
Intent: Convert one interface to another that clients expect.
// Legacy interface we can't change
class LegacyPrinter {
void printDocument(String text) { System.out.println(text); }
}
// Target interface clients expect
interface ModernPrinter {
void print(Document doc);
}
// Adapter
class PrinterAdapter implements ModernPrinter {
private final LegacyPrinter legacy;
PrinterAdapter(LegacyPrinter legacy) { this.legacy = legacy; }
@Override
public void print(Document doc) {
legacy.printDocument(doc.getText());
}
}
Bridge
Intent: Decouple an abstraction from its implementation so both can vary independently.
// Implementation hierarchy
interface Renderer {
void renderCircle(float radius);
}
class VectorRenderer implements Renderer {
public void renderCircle(float r) { System.out.println("Vector circle r=" + r); }
}
class RasterRenderer implements Renderer {
public void renderCircle(float r) { System.out.println("Raster circle r=" + r); }
}
// Abstraction hierarchy
abstract class Shape {
protected Renderer renderer;
Shape(Renderer r) { this.renderer = r; }
abstract void draw();
}
class Circle extends Shape {
private float radius;
Circle(Renderer r, float radius) { super(r); this.radius = radius; }
void draw() { renderer.renderCircle(radius); }
}
// Usage: mix and match
new Circle(new VectorRenderer(), 5).draw();
new Circle(new RasterRenderer(), 5).draw();
When to use: You'd otherwise have a cartesian explosion of subclasses (Shape x Renderer = too many classes).
Composite
Intent: Compose objects into tree structures and treat individual/composite objects uniformly.
from abc import ABC, abstractmethod
class FileSystemNode(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def size(self) -> int: ...
@abstractmethod
def display(self, indent: int = 0): ...
class File(FileSystemNode):
def __init__(self, name: str, size: int):
super().__init__(name)
self._size = size
def size(self) -> int:
return self._size
def display(self, indent=0):
print(" " * indent + f"📄 {self.name} ({self._size}B)")
class Directory(FileSystemNode):
def __init__(self, name: str):
super().__init__(name)
self.children: list[FileSystemNode] = []
def add(self, node: FileSystemNode):
self.children.append(node)
return self
def size(self) -> int:
return sum(child.size() for child in self.children)
def display(self, indent=0):
print(" " * indent + f"📁 {self.name} ({self.size()}B)")
for child in self.children:
child.display(indent + 2)
Decorator
Intent: Attach additional behavior to objects dynamically, without altering their class.
Java — I/O Streams (classic example)
// Each wrapper adds behavior
InputStream in = new BufferedInputStream( // adds buffering
new GZIPInputStream( // adds decompression
new FileInputStream("data.gz") // base stream
)
);
// Custom decorator
interface DataSource {
String readData();
void writeData(String data);
}
class EncryptionDecorator implements DataSource {
private final DataSource wrapped;
EncryptionDecorator(DataSource source) { this.wrapped = source; }
public String readData() {
return decrypt(wrapped.readData());
}
public void writeData(String data) {
wrapped.writeData(encrypt(data));
}
}
Python — Language-level decorators
import functools
import time
def retry(max_attempts=3, delay=1.0):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception:
if attempt == max_attempts - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def fetch_data(url: str):
return requests.get(url).json()
Go — HTTP Middleware
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !isAuthenticated(r) {
http.Error(w, "unauthorized", 401)
return
}
next.ServeHTTP(w, r)
})
}
// Stack decorators
handler := Logging(Auth(myHandler))
| Aspect | Decorator | Inheritance |
|---|---|---|
| When | Runtime | Compile time |
| Combinations | Mix and match freely | Class explosion |
| Transparency | Same interface | New class |
| Complexity | Many small objects | Deep hierarchies |
Facade
Intent: Provide a simplified interface to a complex subsystem.
// Complex subsystem
class VideoCodec { /* ... */ }
class AudioMixer { /* ... */ }
class BitrateReader { /* ... */ }
class VideoFile { /* ... */ }
// Facade
class VideoConverter {
public File convert(String filename, String format) {
VideoFile file = new VideoFile(filename);
VideoCodec codec = CodecFactory.extract(file);
byte[] rawVideo = BitrateReader.read(file, codec);
byte[] audio = AudioMixer.extract(file);
return Muxer.combine(rawVideo, audio, format);
}
}
// Client only needs this:
var converter = new VideoConverter();
File mp4 = converter.convert("video.ogg", "mp4");
Flyweight
Intent: Share common state across many objects to minimize memory usage.
// Flyweight: shared tree type data
class TreeType {
final String name;
final String color;
final byte[] texture; // expensive shared data
TreeType(String name, String color, byte[] texture) {
this.name = name; this.color = color; this.texture = texture;
}
void draw(int x, int y) { /* render at position */ }
}
// Factory ensures sharing
class TreeTypeFactory {
private static Map<String, TreeType> cache = new HashMap<>();
static TreeType get(String name, String color) {
String key = name + ":" + color;
return cache.computeIfAbsent(key,
k -> new TreeType(name, color, loadTexture(name)));
}
}
// Context: unique per-tree data
class Tree {
int x, y; // extrinsic (unique) state
TreeType type; // intrinsic (shared) state
}
- Java
Integer.valueOf()caches -128 to 127 - Python caches small ints (-5 to 256) and interned strings
- String pools in Java / Go
Proxy
Intent: Provide a surrogate or placeholder to control access to another object.
| Type | Purpose | Example |
|---|---|---|
| Virtual | Lazy initialization | Load image only when displayed |
| Protection | Access control | Check permissions before operation |
| Remote | Network transparency | RPC stub, gRPC client |
| Caching | Cache results | Memoize expensive computation |
| Logging | Record operations | Audit trail |
class CachingProxy:
def __init__(self, service):
self._service = service
self._cache: dict[str, Any] = {}
def fetch(self, key: str):
if key not in self._cache:
self._cache[key] = self._service.fetch(key)
return self._cache[key]
Behavioral Patterns
Define how objects communicate and distribute responsibility.
Chain of Responsibility
Intent: Pass a request along a chain of handlers; each decides to process or pass it on.
abstract class Handler {
private Handler next;
Handler setNext(Handler h) { this.next = h; return h; }
void handle(Request req) {
if (next != null) next.handle(req);
}
}
class AuthHandler extends Handler {
void handle(Request req) {
if (!req.isAuthenticated()) {
throw new UnauthorizedException();
}
super.handle(req); // pass to next
}
}
class RateLimitHandler extends Handler {
void handle(Request req) {
if (isRateLimited(req.getIp())) {
throw new TooManyRequestsException();
}
super.handle(req);
}
}
class LoggingHandler extends Handler {
void handle(Request req) {
log.info("Processing: {}", req);
super.handle(req);
}
}
// Build chain
Handler chain = new LoggingHandler();
chain.setNext(new AuthHandler())
.setNext(new RateLimitHandler());
Command
Intent: Encapsulate a request as an object, enabling undo/redo, queueing, and logging.
interface Command {
void execute();
void undo();
}
class AddTextCommand implements Command {
private final Document doc;
private final String text;
private final int position;
AddTextCommand(Document doc, String text, int position) {
this.doc = doc; this.text = text; this.position = position;
}
public void execute() { doc.insert(position, text); }
public void undo() { doc.delete(position, text.length()); }
}
// Command history for undo/redo
class CommandHistory {
private final Deque<Command> history = new ArrayDeque<>();
void execute(Command cmd) {
cmd.execute();
history.push(cmd);
}
void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
Iterator
Intent: Provide sequential access to elements without exposing internal representation.
| Language | Mechanism | Example |
|---|---|---|
| Java | Iterable<T> / Iterator<T> | Enhanced for-loop |
| Python | __iter__ / __next__ | for x in collection |
| Go | range, closures, channels | for k, v := range m |
| JavaScript | Symbol.iterator | for...of |
Python — Generator as Iterator
def fibonacci(limit: int):
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
for num in fibonacci(100):
print(num) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Mediator
Intent: Reduce chaotic many-to-many dependencies by introducing a central coordinator.
class EventBus:
def __init__(self):
self._handlers: dict[str, list[Callable]] = {}
def subscribe(self, event: str, handler: Callable):
self._handlers.setdefault(event, []).append(handler)
def publish(self, event: str, data=None):
for handler in self._handlers.get(event, []):
handler(data)
# Components don't know about each other
bus = EventBus()
bus.subscribe("order_placed", inventory.reserve)
bus.subscribe("order_placed", email.send_confirmation)
bus.subscribe("order_placed", analytics.track)
Memento
Intent: Capture and externalize an object's state for later restoration, without violating encapsulation.
class Editor {
private String content;
private int cursorPos;
// Creates memento
Snapshot save() {
return new Snapshot(content, cursorPos);
}
// Restores from memento
void restore(Snapshot s) {
this.content = s.content();
this.cursorPos = s.cursorPos();
}
record Snapshot(String content, int cursorPos) {}
}
// Caretaker
Deque<Editor.Snapshot> history = new ArrayDeque<>();
history.push(editor.save());
// ... user makes changes ...
editor.restore(history.pop()); // undo
Observer
Intent: Define a one-to-many dependency so that when one object changes state, all dependents are notified.
interface EventListener {
void update(String event, Object data);
}
class EventManager {
private final Map<String, List<EventListener>> listeners = new HashMap<>();
void subscribe(String event, EventListener listener) {
listeners.computeIfAbsent(event, k -> new ArrayList<>()).add(listener);
}
void unsubscribe(String event, EventListener listener) {
listeners.getOrDefault(event, List.of()).remove(listener);
}
void notify(String event, Object data) {
for (var listener : listeners.getOrDefault(event, List.of())) {
listener.update(event, data);
}
}
}
Go — Channels as observers
type Broker struct {
subscribers map[string][]chan Event
mu sync.RWMutex
}
func (b *Broker) Subscribe(topic string) <-chan Event {
ch := make(chan Event, 10)
b.mu.Lock()
b.subscribers[topic] = append(b.subscribers[topic], ch)
b.mu.Unlock()
return ch
}
func (b *Broker) Publish(topic string, e Event) {
b.mu.RLock()
defer b.mu.RUnlock()
for _, ch := range b.subscribers[topic] {
select {
case ch <- e:
default: // drop if full
}
}
}
| Pattern | Coupling | Delivery | Use case |
|---|---|---|---|
| Observer | Subject knows observers | Synchronous | UI events, in-process |
| Pub/Sub | Decoupled via broker | Async possible | Microservices, Kafka |
| Event Bus | Central dispatcher | Usually sync | In-app event system |
State
Intent: Allow an object to alter its behavior when its internal state changes.
interface OrderState {
void next(Order order);
void cancel(Order order);
String status();
}
class PendingState implements OrderState {
public void next(Order o) { o.setState(new ProcessingState()); }
public void cancel(Order o) { o.setState(new CancelledState()); }
public String status() { return "PENDING"; }
}
class ProcessingState implements OrderState {
public void next(Order o) { o.setState(new ShippedState()); }
public void cancel(Order o) { throw new IllegalStateException("Cannot cancel"); }
public String status() { return "PROCESSING"; }
}
class ShippedState implements OrderState {
public void next(Order o) { o.setState(new DeliveredState()); }
public void cancel(Order o) { throw new IllegalStateException("Already shipped"); }
public String status() { return "SHIPPED"; }
}
class Order {
private OrderState state = new PendingState();
void setState(OrderState s) { this.state = s; }
void next() { state.next(this); }
void cancel() { state.cancel(this); }
String status() { return state.status(); }
}
Strategy
Intent: Define a family of interchangeable algorithms encapsulated behind a common interface.
Java
interface CompressionStrategy {
byte[] compress(byte[] data);
}
class ZipStrategy implements CompressionStrategy {
public byte[] compress(byte[] data) { /* zip */ return data; }
}
class GzipStrategy implements CompressionStrategy {
public byte[] compress(byte[] data) { /* gzip */ return data; }
}
class Compressor {
private CompressionStrategy strategy;
Compressor(CompressionStrategy s) { this.strategy = s; }
void setStrategy(CompressionStrategy s) { this.strategy = s; }
byte[] compress(byte[] data) { return strategy.compress(data); }
}
// Java shortcut: lambdas ARE strategies
Compressor c = new Compressor(data -> Arrays.copyOf(data, data.length));
Python — Functions as strategies
from typing import Callable
def sort_by_price(items: list[Product]) -> list[Product]:
return sorted(items, key=lambda p: p.price)
def sort_by_rating(items: list[Product]) -> list[Product]:
return sorted(items, key=lambda p: -p.rating)
def display_products(items: list[Product], strategy: Callable):
for product in strategy(items):
print(product)
Template Method
Intent: Define the skeleton of an algorithm, deferring specific steps to subclasses.
abstract class DataMiner {
// Template method — final to prevent override
public final void mine(String path) {
var file = openFile(path);
var data = extractData(file);
var parsed = parseData(data);
analyzeData(parsed);
generateReport(parsed);
closeFile(file);
}
abstract Object openFile(String path);
abstract String extractData(Object file);
abstract List<Record> parseData(String data);
// Hook methods — optional override
void analyzeData(List<Record> data) { /* default: no-op */ }
void generateReport(List<Record> data) { /* default report */ }
void closeFile(Object file) { /* default close */ }
}
class CSVDataMiner extends DataMiner {
Object openFile(String path) { return new FileReader(path); }
String extractData(Object file) { /* read CSV */ return "..."; }
List<Record> parseData(String data) { /* parse CSV rows */ return List.of(); }
}
Visitor
Intent: Add new operations to existing object structures without modifying them (double dispatch).
// Element hierarchy
interface Shape {
void accept(ShapeVisitor visitor);
}
record Circle(double radius) implements Shape {
public void accept(ShapeVisitor v) { v.visit(this); }
}
record Rectangle(double w, double h) implements Shape {
public void accept(ShapeVisitor v) { v.visit(this); }
}
// Visitor — add operations without modifying shapes
interface ShapeVisitor {
void visit(Circle c);
void visit(Rectangle r);
}
class AreaCalculator implements ShapeVisitor {
double total = 0;
public void visit(Circle c) { total += Math.PI * c.radius() * c.radius(); }
public void visit(Rectangle r) { total += r.w() * r.h(); }
}
class SvgExporter implements ShapeVisitor {
StringBuilder svg = new StringBuilder();
public void visit(Circle c) { svg.append("<circle r='" + c.radius() + "'/>"); }
public void visit(Rectangle r) { svg.append("<rect w='" + r.w() + "' h='" + r.h() + "'/>"); }
}
Python — functools.singledispatch alternative
from functools import singledispatch
@singledispatch
def area(shape):
raise NotImplementedError
@area.register
def _(c: Circle):
return math.pi * c.radius ** 2
@area.register
def _(r: Rectangle):
return r.width * r.height
Modern & Architectural Patterns Modern
Patterns that emerged from DDD, microservices, and cloud-native architecture.
Dependency Injection
Intent: Invert control of object creation — pass dependencies in instead of creating them internally.
// WITHOUT DI — tightly coupled
class OrderService {
private final PaymentGateway gateway = new StripeGateway(); // hard-coded!
}
// WITH DI — constructor injection
class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) { // injected
this.gateway = gateway;
}
}
// In tests: inject mock
var service = new OrderService(new MockPaymentGateway());
Go — Constructor injection (no framework needed)
type OrderService struct {
gateway PaymentGateway // interface
repo OrderRepository // interface
}
func NewOrderService(gw PaymentGateway, repo OrderRepository) *OrderService {
return &OrderService{gateway: gw, repo: repo}
}
// Production
svc := NewOrderService(stripe.New(), postgres.NewOrderRepo(db))
// Test
svc := NewOrderService(&mockGateway{}, &mockRepo{})
- Java: Spring (@Autowired), Guice, Dagger
- Python: dependency-injector, or just pass args
- Go: wire (code-gen), or manual constructors (common)
Repository Pattern
Intent: Abstract data access behind a collection-like interface. Business logic never touches SQL/ORM directly.
// Repository interface
interface UserRepository {
Optional<User> findById(UUID id);
List<User> findByEmail(String email);
User save(User user);
void delete(UUID id);
}
// Production implementation
class JpaUserRepository implements UserRepository {
private final EntityManager em;
// ... JPA queries
}
// Test implementation
class InMemoryUserRepository implements UserRepository {
private final Map<UUID, User> store = new HashMap<>();
// ... map operations
}
Circuit Breaker Resilience
Intent: Prevent cascading failures by wrapping calls to external services with a fail-fast mechanism.
States: Closed (normal) → Open (failures hit threshold, fast-fail) → Half-Open (try one request to test recovery)
import time
from enum import Enum
class State(Enum):
CLOSED = "closed"
OPEN = "open"
HALF_OPEN = "half_open"
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.state = State.CLOSED
self.failures = 0
self.threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = 0
def call(self, func, *args, **kwargs):
if self.state == State.OPEN:
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = State.HALF_OPEN
else:
raise CircuitOpenError("Circuit is open")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
def _on_success(self):
self.failures = 0
self.state = State.CLOSED
def _on_failure(self):
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.threshold:
self.state = State.OPEN
CQRS Architecture
Intent: Separate the read model from the write model. Queries and commands have different data representations.
| Aspect | Simple CRUD | CQRS |
|---|---|---|
| Model | Single model for read/write | Separate read + write models |
| Consistency | Strong | Eventual (usually) |
| Complexity | Low | High |
| Scaling | Scale together | Scale independently |
| Best for | Simple domains | Complex domains, high-read systems |
// Command side — optimized for writes
class CreateOrderCommand {
UUID customerId;
List<OrderItem> items;
}
class OrderCommandHandler {
void handle(CreateOrderCommand cmd) {
var order = Order.create(cmd.customerId, cmd.items);
orderRepo.save(order);
eventBus.publish(new OrderCreatedEvent(order.getId()));
}
}
// Query side — optimized for reads (denormalized)
class OrderSummaryQuery {
UUID orderId;
}
class OrderQueryHandler {
OrderSummaryDTO handle(OrderSummaryQuery q) {
return readDb.findOrderSummary(q.orderId); // flat, pre-joined view
}
}
Event Sourcing Architecture
Intent: Store state changes as an immutable sequence of events rather than mutable current state.
// Events (immutable facts)
sealed interface AccountEvent {
record Opened(UUID id, String owner, Instant at) implements AccountEvent {}
record Deposited(UUID id, BigDecimal amount, Instant at) implements AccountEvent {}
record Withdrawn(UUID id, BigDecimal amount, Instant at) implements AccountEvent {}
}
// Rebuild state by replaying events
class Account {
UUID id;
BigDecimal balance = BigDecimal.ZERO;
static Account replay(List<AccountEvent> events) {
var account = new Account();
for (var event : events) {
account.apply(event);
}
return account;
}
void apply(AccountEvent event) {
switch (event) {
case AccountEvent.Opened e -> { this.id = e.id(); }
case AccountEvent.Deposited e -> { this.balance = balance.add(e.amount()); }
case AccountEvent.Withdrawn e -> { this.balance = balance.subtract(e.amount()); }
}
}
}
- Schema evolution — old events must remain parseable as the schema changes
- Replay performance — use snapshots for entities with many events
- Eventual consistency — projections may lag behind writes
Saga Pattern Distributed
Intent: Manage distributed transactions across microservices using a sequence of local transactions with compensating actions.
| Style | Choreography | Orchestration |
|---|---|---|
| Control | Decentralized (events) | Central coordinator |
| Coupling | Low | Orchestrator knows all steps |
| Complexity | Hard to trace | Easier to understand |
| Best for | Simple flows, few steps | Complex flows, many steps |
# Orchestration saga
class OrderSaga:
def execute(self, order):
try:
payment_id = payment_service.charge(order.total)
except PaymentError:
raise # nothing to compensate
try:
inventory_service.reserve(order.items)
except InventoryError:
payment_service.refund(payment_id) # compensate step 1
raise
try:
shipping_service.schedule(order)
except ShippingError:
inventory_service.release(order.items) # compensate step 2
payment_service.refund(payment_id) # compensate step 1
raise
Sidecar / Ambassador / Anti-corruption Layer
| Pattern | Purpose | Example |
|---|---|---|
| Sidecar | Attach helper alongside main service | Envoy proxy for mTLS, logging agent, Dapr |
| Ambassador | Proxy for remote service calls | Connection pooling, retry logic, circuit breaking |
| Anti-corruption Layer | Isolate legacy system interfaces | Translate between old/new domain models at boundary |
Concurrency Patterns
Producer-Consumer
Intent: Decouple production of data from consumption using a buffer/queue.
Go — Channels
func producer(ch chan<- int) {
for i := 0; i < 100; i++ {
ch <- i
}
close(ch)
}
func consumer(id int, ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for item := range ch {
process(item)
}
}
func main() {
ch := make(chan int, 10) // buffered
var wg sync.WaitGroup
go producer(ch)
for i := 0; i < 4; i++ {
wg.Add(1)
go consumer(i, ch, &wg)
}
wg.Wait()
}
Java — BlockingQueue
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);
// Producer thread
executor.submit(() -> {
while (running) {
queue.put(generateTask()); // blocks if full
}
});
// Consumer threads
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
while (running) {
Task task = queue.take(); // blocks if empty
task.process();
}
});
}
Worker Pool
Intent: Fixed number of workers processing tasks from a shared queue.
func workerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- job.Process()
}
}()
}
wg.Wait()
close(results)
}
Python — concurrent.futures
from concurrent.futures import ThreadPoolExecutor, as_completed
def process_url(url: str) -> dict:
return requests.get(url).json()
urls = ["https://api.example.com/1", "https://api.example.com/2", ...]
with ThreadPoolExecutor(max_workers=10) as pool:
futures = {pool.submit(process_url, url): url for url in urls}
for future in as_completed(futures):
url = futures[future]
result = future.result() # raises if task failed
Fan-Out / Fan-In
Intent: Distribute work across multiple workers (fan-out), then merge results (fan-in).
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
channels[i] = worker(input)
}
return channels
}
func fanIn(channels ...<-chan int) <-chan int {
merged := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
merged <- v
}
}(ch)
}
go func() { wg.Wait(); close(merged) }()
return merged
}
Reactor / Event Loop
Intent: Handle many concurrent I/O operations with a single thread dispatching events.
| Implementation | Language | Model |
|---|---|---|
| asyncio | Python | Single-threaded event loop + coroutines |
| Node.js | JavaScript | libuv event loop + callbacks/promises |
| Java NIO | Java | Selector + channels (Netty wraps this) |
| Go runtime | Go | M:N scheduler (goroutines on OS threads) |
Anti-Patterns
Patterns to recognize and avoid.
| Anti-Pattern | Symptoms | Fix |
|---|---|---|
| God Object | One class does everything, 1000+ lines, imported everywhere | Single Responsibility Principle, extract classes |
| Singleton Abuse | Global state everywhere, untestable, hidden dependencies | Use dependency injection |
| Premature Abstraction | Interfaces with one implementation, "what if" abstractions | Rule of three: wait until 3 uses to abstract |
| Golden Hammer | Using the same pattern/tool for every problem | Choose patterns based on the actual problem |
| Lava Flow | Dead code nobody dares delete, mysterious utils | Delete it. Git has history. Tests catch breakage. |
| Cargo Cult | Copying patterns without understanding why | Understand the problem before applying a pattern |
| Anemic Domain | Entities are just data bags, all logic in services | Rich domain model: behavior belongs on the entity |
| Leaky Abstraction | Callers must understand internals to use correctly | Better encapsulation, expose clean contracts |
if/else don't need a Strategy. A simple class doesn't need a Builder. Use the simplest thing that works.
Pattern Selection Guide
Decision Table
| I need to... | Consider |
|---|---|
| Create objects without specifying exact class | Factory Method, Abstract Factory |
| Build complex objects with many options | Builder |
| Ensure only one instance exists | Singleton (or just DI) |
| Convert an incompatible interface | Adapter |
| Add behavior without modifying existing code | Decorator, Visitor |
| Simplify a complex subsystem API | Facade |
| Represent tree/hierarchy structures | Composite |
| Swap algorithms at runtime | Strategy |
| React to state changes in another object | Observer, Event Bus |
| Implement undo/redo | Command + Memento |
| Handle requests through a pipeline | Chain of Responsibility |
| Change behavior based on state | State |
| Traverse collections uniformly | Iterator |
| Handle distributed transactions | Saga |
| Prevent cascading failures | Circuit Breaker |
| Separate read/write concerns | CQRS |
| Keep complete audit history | Event Sourcing |
Language Idioms That Replace Patterns
Some GoF patterns exist because of language limitations. Modern languages often have built-in support:
| GoF Pattern | Language Feature That Replaces It |
|---|---|
| Strategy | First-class functions (Python, Go, JS, Java lambdas) |
| Iterator | Generators (Python), range (Go), for...of (JS) |
| Decorator | Python @decorator, Go middleware, JS decorators |
| Observer | Go channels, JS EventEmitter, Python signals |
| Singleton | Python modules, Go sync.Once |
| Builder | Go functional options, Python kwargs + dataclasses |
| Adapter | Go implicit interfaces (structural typing) |
| Command | First-class functions / closures in any language |
| Template Method | Python ABC, Go interfaces + embedding |
| Visitor | Python singledispatch, Java sealed + switch (21+) |