Introduction to Design Patterns
A design pattern is a named solution to a recurring design problem in a context, together with its consequences. The name is the least important part — it is a handle for a conversation, not a trophy.
The Gang of Four (GoF) catalogue is organised by purpose:
| Family | Purpose | Examples |
|---|---|---|
| Creational | control how objects are created | Factory Method, Abstract Factory, Builder, Singleton, Prototype |
| Structural | compose objects into larger structures | Adapter, Decorator, Facade, Proxy, Composite, Bridge |
| Behavioural | distribute responsibility and communication | Strategy, Observer, Command, State, Template Method, Iterator |
Modern languages absorb some patterns: first-class functions replace many Strategy classes, modules replace some Singletons, async iterators replace some Iterators. The problem remains; the implementation changes.
The anatomy of a pattern
Every pattern has five parts. If you cannot fill them in, do not use it:
- Problem — what goes wrong without it (e.g., callers depend on concrete classes).
- Context — when the problem appears (e.g., multiple providers selected at runtime).
- Forces — competing constraints (extensibility vs simplicity, performance vs clarity).
- Solution — the structure and responsibilities.
- Consequences — what gets easier, what gets harder, what new failure modes appear.
Most pattern descriptions in the wild give only the Solution. That is why they produce code that looks like the diagram and solves nothing.
Patterns vs principles vs idioms
| Layer | Example | Nature |
|---|---|---|
| Principle | Dependency Inversion | a value/goal, always applicable |
| Pattern | Strategy | a reusable structure for a recurring problem |
| Idiom | injecting a Clock instead of calling Date.now() |
language/team-specific habit |
Principles guide whether to abstract. Patterns are one way to structure the abstraction. Idioms are how it looks in your language.
When to introduce a pattern
Patterns are a response to observed variation or a real external boundary:
- You have a
switchthat grows with every release → Strategy / polymorphic policy. - Construction needs configuration, validation, or a lifecycle → Factory / Builder.
- Multiple objects must react to state changes without tight coupling → Observer.
- A third-party API leaks into your domain → Adapter.
- Cross-cutting behaviour (retry, caching, auth) must wrap a call → Decorator.
Signals that the pattern is premature:
- You cannot name the second implementation.
- The abstraction makes the common path harder to read.
- You are writing a factory for a constructor with two arguments.
- The pattern's name appears in class names (
AbstractFactoryImpl) but the problem does not.
Refactoring to patterns
The healthy order is:
- Write the straightforward implementation.
- Watch for duplication and repeated edits (the rule of three).
- Extract the variation point.
- Only then name it with a pattern if the name helps the team.
Starting from the pattern and inventing a problem is how you get OrderFactoryFactory.
Patterns in this section
| Pattern | Problem it solves | Biggest risk |
|---|---|---|
| Factory | construction logic is scattered or type-dependent | hidden control flow, class explosion |
| Singleton | one shared instance with controlled access | global state, test coupling |
| Strategy | branching on policy/algorithm | clients must know which strategy to pick |
| Observer | many listeners for one event | leaks, error storms, ordering ambiguity |
Each chapter covers the problem first, then the structure, then when not to use it.
A pattern is a conversation shortcut
Patterns are valuable when a team shares the vocabulary:
"This is an Observer, so unsubscribing matters." "That's a Decorator; retries compose around the client rather than editing it."
Without shared context, the name adds nothing and can hide complexity. "A list of functions called after an order is placed" is often clearer than "an Observer pipeline" to a new teammate.
[!TIP] ELI5: Patterns are recipes, not ingredients A recipe tells you when to use it, what it needs, and what can go wrong. "Add a Factory" is like adding flour to every dish because a recipe used flour. Start from the dish (the change), then choose the recipe.
The pattern review test
Before adding a pattern, answer:
- What specific change becomes cheaper?
- Which existing files stop changing?
- What new indirection must a reader now follow?
- What can go wrong that could not before (ordering, lifecycle, hidden state)?
- Is there a simpler language feature (function, module, map) that solves it?
If the answers are vague, the pattern is decoration.
Exercises
- Find a pattern in your codebase. Write its problem, forces, and consequences in five sentences. If you cannot identify the problem, delete the abstraction.
- Replace one Strategy class hierarchy with a map of functions. Count the lines and compare readability.
- List every
switchon a domain type. Rank them by how often they change; abstract only the top one. - Explain to a teammate why a Singleton is a design decision, not a convenience.
Checklist
- I can state the problem, forces, and consequences without naming the pattern.
- There is a real second variant or external boundary.
- The pattern reduces the blast radius of the next expected change.
- The common path is still easy to read and debug.
- A simpler language feature was considered first.
- The team shares the vocabulary and the trade-off.
Next: Factory — putting construction in one place without hiding it.