Design Patterns Selection Guide
Design patterns are named arrangements of responsibilities and communication. They are useful when they directly address a recurring change or coordination problem. They are harmful when added merely to make code “look architected.”
Start with the pressure, not the pattern
| Pressure or problem | Candidate pattern |
|---|---|
| Clients face a complicated subsystem | Facade |
| Actions must be queued, logged, retried, replayed, or undone | Command |
| One algorithm varies independently from its caller | Strategy |
| An algorithm skeleton is fixed but selected steps vary | Template Method |
| Multiple dependents react to an event/state change | Observer |
| Behavior changes according to explicit lifecycle state | State |
| Individual objects and nested groups need one interface | Composite |
| Traverse a collection without exposing representation | Iterator |
| A request passes through optional ordered handlers | Chain of Responsibility |
| An existing interface must satisfy a different contract | Adapter |
| Access to an object needs control, caching, remoting, or lazy loading | Proxy |
| Construction must choose among related concrete types | Factory |
| Construction has many ordered/optional steps | Builder |
| Exactly one coordinated instance is genuinely required | Singleton—use cautiously |
| Add behavior around an object without changing its class | Decorator |
| Add operations across a stable object structure | Visitor |
UML as a thinking and communication tool
Use-case diagram
Clarifies actors, system boundary, and user-visible goals. Use it early to expose missing users or responsibilities. It does not replace acceptance criteria or detailed flow.
Sequence diagram
Shows messages over time between participants. Use it for API calls, authentication, retries, event flows, or any interaction where ordering and ownership matter.
Class diagram
Shows types, responsibilities, inheritance, composition, and associations. Use it to discuss structure, not to generate a class for every noun.
The smallest diagram that resolves the uncertainty is the correct diagram.
Structural Patterns
Facade
Provide a stable, simplified entry point into a complex subsystem.
Client → Facade → subsystem A/B/CUse when clients repeatedly coordinate the same internal steps or should be protected from subsystem churn. Keep lower-level interfaces available when advanced callers genuinely need them.
Failure mode: the facade becomes a god object containing business rules unrelated to orchestration.
Adapter
Translate one interface into another expected by the client.
Client → ExpectedPort ← Adapter → Existing/ThirdParty APIUse at integration boundaries, during migrations, or when protecting domain code from vendor-specific shapes.
Failure mode: adapters silently change semantics instead of only translating representation/protocol.
Proxy
Stand in for another object while preserving its interface. The proxy may enforce authorization, delay construction, cache results, communicate remotely, or record access.
Failure modes: hidden latency, stale caching, surprising side effects, and authorization logic that differs across access paths.
Composite
Represent leaf objects and nested groups through the same interface. A directory tree, UI hierarchy, organization chart, or expression tree can be treated recursively.
Component
├── Leaf
└── Composite → children: Component[]Failure mode: forcing operations onto leaves that make sense only for containers, producing no-op methods or runtime errors.
Decorator
Wrap an object with the same contract to add behavior dynamically.
Client → LoggingDecorator → CachingDecorator → RealServiceUseful for logging, metrics, retries, compression, formatting, or layered UI behavior.
Failure modes: wrapper order changes behavior; identity/debugging becomes confusing; many tiny decorators obscure the execution path.
Behavioral Patterns
Command
Package a request as an object with an execution contract. Commands separate who requests an action from who performs it.
Useful for queues, jobs, menus, shortcuts, audit trails, macro recording, undo/redo, and replay.
For undo, record enough previous state or define a safe inverse. In distributed systems, prefer idempotency and compensating actions over pretending every effect is reversible.
Strategy
Move interchangeable algorithms behind one interface and inject/select the appropriate implementation.
Examples: route calculation, pricing, sorting, authentication provider, export format, or retry policy.
Prefer Strategy over large conditionals when algorithms change independently and have meaningful isolated tests. Do not create strategies for a single stable two-line branch.
Template Method
A base abstraction defines the ordered algorithm while subclasses override selected steps.
Useful when the sequence is genuinely invariant and variation belongs to a type hierarchy. Prefer composition/Strategy when steps need runtime combination or inheritance becomes rigid.
Observer
A subject/publisher notifies registered observers/subscribers about events or state changes.
Useful for user interfaces, domain events, notifications, and plugin extension points.
Define subscription lifetime, delivery order, failure isolation, duplicate handling, backpressure, and whether events carry facts or mutable state.
Failure modes: memory leaks, invisible dependencies, cascading updates, event loops, and eventual-consistency surprises.
State
Represent lifecycle-specific behavior as state objects rather than a growing matrix of conditionals.
Application: Draft → Submitted → InReview → Accepted/RejectedDefine legal transitions explicitly. Persist state and transition atomically when business invariants depend on them. State is especially useful when the same command means different things in different phases.
Failure mode: using State for a simple enum with almost no behavior.
Iterator
Provide sequential access without exposing a collection’s representation. Iterators centralize traversal state and can support filtering, lazy evaluation, streaming, or custom order.
Failure modes: mutation during traversal, resource lifetime leaks, and accidental repeated traversal of one-shot streams.
Chain of Responsibility
Pass a request through an ordered chain until handlers process, transform, reject, or forward it.
Examples: HTTP middleware, validation pipelines, authentication/authorization, logging, and support escalation.
Make ordering explicit. Define whether multiple handlers may act and what “handled” means. Avoid chains where no one can determine which handler produced the outcome.
Visitor
Place related operations in visitor objects while traversing a stable object structure. Visitor makes new operations easier but makes new element types harder.
Useful for compilers/ASTs, document trees, exporters, static analysis, and interpreters when node kinds are stable and operations grow.
Failure mode: applying Visitor to a rapidly evolving domain model, causing every new type to break every visitor.
Creational Patterns
Factory
Centralize selection and creation of concrete implementations while returning an abstraction.
Use when construction rules, environment, configuration, or supported types vary. A factory should enforce valid creation, not become an unrelated service locator.
Builder
Build a complex object step-by-step, especially when construction has many optional values, validation rules, or meaningful stages.
Builders improve readability and can guarantee validity at build(). For simple immutable records with a few fields, named constructors or language-native initialization are clearer.
Singleton
Ensure one accessible instance. This pattern is often overused.
Possible legitimate cases include a process-wide coordinator around a resource that truly must be unique. Prefer dependency injection with an application-scoped lifetime so uniqueness is managed by composition rather than hidden global access.
Failure modes:
- Hidden dependency/global mutable state
- Test contamination and order dependence
- Concurrency problems
- Multiple “singletons” across processes, workers, tabs, or deployments
- An assumption of global uniqueness where only local uniqueness exists
Pattern Relationships
- Facade changes the interface to simplify a subsystem; Adapter changes it to match a required contract; Proxy preserves the interface while controlling access; Decorator preserves it while adding behavior.
- Strategy varies an entire algorithm; Template Method varies steps through inheritance; State varies behavior according to lifecycle phase.
- Command represents an action; Chain of Responsibility routes an action/request; Observer broadcasts an event after something happens.
- Factory selects/constructs objects; Builder assembles one complex object; Singleton constrains lifetime/count.
- Composite represents recursive structure; Iterator traverses it; Visitor adds operations over it.
Decision checklist
Before introducing a pattern, answer:
- What concrete change or failure is difficult today?
- Which responsibility is misplaced or coupled?
- What simpler solution was considered?
- Which dependency direction improves?
- What new indirection and failure modes appear?
- How will the pattern be tested and explained?
- What evidence would justify removing it later?
AI coding-assistant prompt
Review this design problem without assuming a design pattern is required. Identify the actual change pressure and current coupling. Compare the simplest direct design with applicable patterns. If recommending a pattern, explain participants, dependency direction, execution flow, new failure modes, and a focused test strategy. Reject patterns whose indirection costs exceed their benefit.Source
- Saturngod, Design Patterns, current online edition indexed 2026-08-12.
- Related processed PDF note: Design Patterns.