Design Patterns
Design patterns are reusable solutions to recurring design problems — and just as importantly, they're shared vocabulary. Saying "this is a Strategy" communicates intent and structure in two words. The classic catalog splits into three families: creational (how objects are made), structural (how they're composed), and behavioral (how they interact).
Patterns are tools, not goals. The best code reaches for simple composition and clear boundaries first, and introduces a pattern only when it reduces duplication or makes a likely change safer. If you can't name the future change you're protecting against, adding a pattern usually adds complexity, not value.
TL;DR
- Patterns are tools, not goals — and shared vocabulary.
- Prefer simple composition and clear boundaries first.
- Use a pattern when it reduces duplication and makes change safer.
- If you can't name the change you're guarding against, don't add it.
Quick Example
The Strategy pattern — swap behavior at runtime behind a common interface:
Creational Patterns
How objects get created.
- Factory — centralize creation when it's complex or config-dependent; hides concrete implementations behind a single point.
- Builder — construct objects with many optional parameters or steps, one readable call at a time.
- Singleton (use carefully) — creates global state and usually hurts testability. Prefer dependency injection or a module-level instance with a clear seam.
Structural Patterns
How objects are composed.
- Adapter — wrap a dependency to match your interface (e.g. adapt a third-party SDK to your own
EmailProvider). - Facade — present a simpler API over a complex subsystem (e.g.
BillingServicehiding several payment calls). - Decorator — add behavior without changing the underlying implementation; common for logging, caching, and retries.
Behavioral Patterns
How objects interact.
- Strategy — swap algorithms/behavior at runtime (see the Quick Example) — e.g. per-customer pricing.
- Observer / Pub-Sub — notify many listeners when something happens. See Event-Driven Architecture.
- Command — encapsulate an action plus its parameters; useful for queues/background jobs and undo/redo. See Message Queues.
How to Choose a Pattern
Ask three questions before reaching for one:
- What change do we expect?
- What coupling is causing pain right now?
- Does this pattern reduce risk and increase clarity?
If the answers are vague, the pattern is probably premature.
Best Practices
- Start simple — plain functions and composition before pattern machinery.
- Name the change a pattern protects against before adding it.
- Use patterns as vocabulary — name them in code and reviews to communicate intent.
- Prefer dependency injection over Singletons for shared services.
- Favor composition over deep inheritance hierarchies.
Common Mistakes
Pattern-oriented programming
Singleton as hidden dependency injection
FAQ
Do I still need design patterns in modern / functional code?
The classic GoF patterns are framed in OOP terms, but the underlying problems persist everywhere. Functional languages often express the same intent more directly — a Strategy is just passing a function, a Decorator is function composition, an Observer is a stream/subscription. Learn the patterns for their vocabulary and the problems they solve; apply whatever idiom your language makes natural.
When is a pattern over-engineering?
When it guards against a change that isn't actually coming. A factory with one product type, a strategy interface with a single implementation, or an abstraction layer over a dependency you'll never swap all add indirection without benefit. The test: can you name the concrete future change or current pain the pattern addresses? If not, prefer the simpler direct code.
What's the difference between a design pattern and an architectural pattern?
Design patterns (factory, strategy, observer) operate at the class/object level — how a small cluster of objects is structured. Architectural patterns (layered, microservices, event-driven, hexagonal) operate at the system level — how major components and boundaries are arranged. They complement each other: you use design patterns within an architecture. See Clean Architecture and Microservices.
How do design patterns relate to SOLID?
Patterns are concrete recipes that frequently realize SOLID principles: Strategy and Decorator enable the Open/Closed Principle, Adapter and dependency injection support Dependency Inversion. SOLID provides the goals (manage change, reduce coupling); patterns provide proven implementations. Knowing the principles keeps you from applying patterns decoratively. See SOLID Principles.
Related Topics
- SOLID Principles — The goals patterns serve
- Clean Architecture — Patterns at the boundary
- Event-Driven Architecture — Observer at system scale
- Microservices — Architectural patterns
- Domain-Driven Design — Modeling with intent