Design Patterns
Overview
Design patterns are proven solutions to recurring software design problems. They provide a shared vocabulary for discussing design and capture collective wisdom refined through decades of real-world use.
Core Philosophy: Patterns are templates you adapt to your context, not blueprints to copy. Use the right pattern when it genuinely simplifies your design—not to impress or over-engineer.
Foundational Principles
These principles underpin all good design:
| Principle | Meaning | Violation Symptom | |-----------|---------|-------------------| | Encapsulate What Varies | Isolate changing parts from stable parts | Changes ripple through codebase | | Program to Interfaces | Depend on abstractions, not concretions | Can't swap implementations | | Composition Over Inheritance | Build behavior by composing objects | Deep rigid class hierarchies | | Loose Coupling | Minimize interdependency between objects | Can't change one thing without breaking another | | Open-Closed | Open for extension, closed for modification | Must edit existing code for new features | | Single Responsibility | One reason to change per class | Classes doing too many things | | Dependency Inversion | High-level modules don't depend on low-level | Business logic coupled to infrastructure |
Pattern Selection Guide
By Problem Type
CREATING OBJECTS
├── Complex/conditional creation ──────────→ Factory Method
├── Families of related objects ───────────→ Abstract Factory
├── Step-by-step construction ─────────────→ Builder
├── Clone existing objects ────────────────→ Prototype
└── Single instance needed ────────────────→ Singleton (use sparingly!)
STRUCTURING/COMPOSING OBJECTS
├── Incompatible interface ────────────────→ Adapter
├── Simplify complex subsystem ────────────→ Facade
├── Tree/hierarchy structure ──────────────→ Composite
├── Add behavior dynamically ──────────────→ Decorator
└── Control access to object ──────────────→ Proxy
MANAGING COMMUNICATION/BEHAVIOR
├── One-to-many notification ──────────────→ Observer
├── Encapsulate requests as objects ───────→ Command
├── Behavior varies by internal state ─────→ State
├── Swap algorithms at runtime ────────────→ Strategy
├── Algorithm skeleton with hooks ─────────→ Template Method
├── Reduce N-to-N communication ───────────→ Mediator
└── Sequential handlers ───────────────────→ Chain of Responsibility
MANAGING DATA ACCESS
├── Abstract data source ──────────────────→ Repository
├── Track changes for atomic commit ───────→ Unit of Work
├── Ensure object identity ────────────────→ Identity Map
├── Defer expensive loading ───────────────→ Lazy Load
├── Map objects to database ───────────────→ Data Mapper
└── Shape data for transfer ───────────────→ DTO
By Symptom
| Symptom | Consider | |---------|----------| | Giant switch/if-else on type | Strategy, State, or polymorphism | | Duplicate code across classes | Template Method, Strategy | | Need to notify many objects of changes | Observer | | Complex object creation logic | Factory, Builder | | Adding features bloats class | Decorator | | Third-party API doesn't fit your code | Adapter | | Too many dependencies between components | Mediator, Facade | | Can't test without database/network | Repository, Dependency Injection | | Need undo/redo | Command | | Object behavior depends on state | State | | Request needs processing by multiple handlers | Chain of Responsibility |
Domain Logic: Transaction Script vs Domain Model
| Factor | Transaction Script | Domain Model | |--------|-------------------|--------------| | Logic complexity | Simple (< 500 lines) | Complex, many rules | | Business rules | Few, straightforward | Many, interacting | | Operations | CRUD-heavy | Rich behavior | | Team/timeline | Small team, quick delivery | Long-term maintenance | | Testing | Integration tests | Unit tests on domain |
Rule of thumb: Start with Transaction Script. Refactor to Domain Model when procedural code becomes hard to maintain.
Quick Reference
Tier 1: Essential Patterns (Master First)
| Pattern | One-Line | When to Use | Reference | |---------|----------|-------------|-----------| | Strategy | Encapsulate interchangeable algorithms | Multiple ways to do something, swap at runtime | strategy.md | | Observer | Notify dependents of state changes | Event systems, reactive updates | observer.md | | Factory | Encapsulate object creation | Complex/conditional instantiation | factory.md | | Decorator | Add behavior dynamically | Extend without inheritance | decorator.md | | Command | Encapsulate requests as objects | Undo/redo, queuing, logging | command.md |
Tier 2: Structural Patterns
| Pattern | One-Line | When to Use | Reference | |---------|----------|-------------|-----------| | Adapter | Convert interfaces | Integrate incompatible code | adapter.md | | Facade | Simplify complex subsystems | Hide complexity behind simple API | facade.md | | Composite | Uniform tree structures | Part-whole hierarchies | composite.md | | Proxy | Control access to objects | Lazy load, access control, caching | proxy.md |
Tier 3: Enterprise/Architectural Patterns
| Pattern | One-Line | When to Use | Reference | |---------|----------|-------------|-----------| | Repository | Collection-like data access | Decouple domain from data layer | repository.md | | Unit of Work | Coordinate atomic changes | Transaction management | unit-of-work.md | | Service Layer | Orchestrate business operations | Define application boundary | service-layer.md | | DTO | Shape data for transfer | API contracts, prevent over-exposure | dto.md |
Additional Important Patterns
| Pattern | One-Line | When to Use | Reference | |---------|----------|-------------|-----------| | Builder | Step-by-step object construction | Complex objects, fluent APIs | builder.md | | State | Behavior changes with state | State machines, workflow | state.md | | Template Method | Algorithm skeleton with hooks | Framework extension points | template-method.md | | Chain of Responsibility | Pass request along handlers | Middleware, pipelines | chain-of-responsibility.md | | Mediator | Centralize complex communication | Reduce component coupling | mediator.md | | Lazy Load | Defer expensive loading | Performance, large object graphs | lazy-load.md | | Identity Map | Ensure object identity | ORM, prevent duplicates | identity-map.md |
Common Mistakes
| Mistake | Symptom | Fix | |---------|---------|-----| | Pattern Overuse | Simple operations require navigating many classes | Only use when solving real problem | | Wrong Pattern | Code feels forced, awkward | Re-examine actual problem | | Inheritance Abuse | Deep hierarchies, fragile base class | Favor composition (Strategy, Decorator) | | Singleton Abuse | Global state, hidden dependencies, hard to test | Use dependency injection instead | | Premature Abstraction | Interfaces with single implementation | Wait for real need to vary |
Anti-Patterns to Recognize
- God Object: One class does everything → Split using SRP
- Anemic Domain Model: Objects are just data bags → Move behavior to objects
- Golden Hammer: Same pattern everywhere → Match pattern to problem
- Lava Flow: Dead code nobody removes → Delete it, VCS has your back
Modern Variations
| Modern Pattern | Based On | Description | |----------------|----------|-------------| | Dependency Injection | Strategy + Factory | Container creates and injects dependencies | | Middleware | Decorator + Chain of Responsibility | Request/response pipeline | | Event Sourcing | Command | Store state changes as events | | CQRS | Command/Query separation | Separate read/write models | | Hooks (React/Vue) | Observer + Strategy | Functional lifecycle subscriptions |
Implementation Checklist
Before implementing a pattern:
- [ ] Pattern solves a real problem in this codebase
- [ ] Considered simpler alternatives
- [ ] Trade-offs acceptable for this context
- [ ] Team understands the pattern
- [ ] Won't over-engineer the solution