Design Patterns — Names for Recurring Solutions
Design Patterns — Names for Recurring Solutions
Solving similar problems repeatedly produces similar shapes. As object orientation went mainstream in the 1990s, a book came out that named and organized those repeated shapes, and patterns became the shared language of dev culture. This article covers the origins of patterns, the classification of the 23 GoF patterns, the meaning of the seven you meet most often, functional equivalents, anti-patterns, and the view that patterns are tools, not goals.
1. About design patterns
The flagship text is Design Patterns: Elements of Reusable Object-Oriented Software, published in 1994. The four authors — Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides — earned the nickname "Gang of Four (GoF)". The book classified 23 patterns and rooted the term "design pattern" in computer science.
The inspiration that predates GoF is architect Christopher Alexander's A Pattern Language (1977). It named recurring good shapes in architecture, and GoF carried the same idea to software.
Later came Martin Fowler's Patterns of Enterprise Application Architecture (2002), tactical patterns from domain-driven design, concurrency patterns, functional patterns — the catalog has expanded to today.
2. Classification of the 23 GoF patterns
| Category | Patterns |
|---|---|
| Creational | Singleton · Factory Method · Abstract Factory · Builder · Prototype |
| Structural | Adapter · Bridge · Composite · Decorator · Facade · Flyweight · Proxy |
| Behavioral | Chain of Responsibility · Command · Iterator · Mediator · Memento · Observer · State · Strategy · Template Method · Visitor · Interpreter |
Memorizing all 23 is rare. Being comfortable with the seven most common ones gets you far enough.
3. Singleton — only one instance
A globally unique object. Configuration objects, loggers, DB pools commonly use this:
// Common shape
const config = { apiUrl: "...", timeout: 5000 };
export default config;
// Class form
class Logger {
static instance;
static getInstance() {
if (!Logger.instance) Logger.instance = new Logger();
return Logger.instance;
}
}
Overuse leads to test difficulties (global state) and multithreaded synchronization issues. The critique "an object-oriented dress on a global variable" comes up often.
4. Factory — delegate creation to a function
Don't call new directly; delegate the choice of which kind to create to a factory function:
function createUser(role) {
if (role === "admin") return new AdminUser();
if (role === "guest") return new GuestUser();
return new RegularUser();
}
The caller doesn't need to know which class is built.
5. Observer — broadcast change
When one object changes, subscribers get notified:
class EventBus {
listeners = new Map();
on(event, fn) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event).push(fn);
}
emit(event, data) {
this.listeners.get(event)?.forEach(fn => fn(data));
}
}
The browser's addEventListener, Node's EventEmitter, and RxJS's Observable are all this pattern.
6. Strategy · Adapter · Decorator · Iterator
Strategy — pick one of several implementations of the same interface at runtime:
const sortStrategies = {
asc: (a, b) => a - b,
desc: (a, b) => b - a,
byName: (a, b) => a.name.localeCompare(b.name),
};
arr.sort(sortStrategies[mode]);
Adapter — when the shape of existing code doesn't match new code, wedge a shape-matcher between them:
function fetchAllPromise() {
return new Promise((resolve, reject) => {
legacyLib.fetchAll((err, data) => err ? reject(err) : resolve(data));
});
}
Decorator — add behavior without changing the object itself:
function withLogging(fn) {
return (...args) => {
console.log("call", fn.name, args);
const r = fn(...args);
console.log("ret", r);
return r;
};
}
const loggedAdd = withLogging((a, b) => a + b);
Python's @decorator, TS's @experimental_decorator, and Java / Spring annotations are the same idea.
Iterator — hide the internal representation and expose only a traversal interface:
const it = arr[Symbol.iterator]();
it.next(); // { value: 0, done: false }
for (const x of arr) { /* ... */ }
for...of, Python's for x in xs, Java's Iterable are all this pattern.
7. Functional equivalents
Some OOP patterns express naturally as functions and closures in functional languages. It's not that "the pattern disappeared" — the language absorbed it as a first-class feature:
| OOP pattern | Functional equivalent |
|---|---|
| Strategy | first-class function as argument |
| Command | closure |
| Observer | reactive stream · pub-sub |
| Iterator | lazy sequence · generator |
| Decorator | higher-order function |
| Template Method | function composition |
| Singleton | module (evaluated once) |
Peter Norvig's 1996 talk Design Patterns in Dynamic Languages is often cited for noting that 16 of the 23 patterns simplify in dynamic languages.
8. Pattern families beyond GoF
- Enterprise patterns (Fowler 2002) — Repository · Unit of Work · DAO · MVC · MVP · MVVM.
- Concurrency patterns — Producer-Consumer · Read-Write Lock · Actor · Future / Promise.
- Distributed system patterns — Circuit Breaker · Bulkhead · Retry · Saga · Event Sourcing · CQRS.
- DDD tactical patterns — Entity · Value Object · Aggregate · Domain Service.
- Functional patterns — Functor · Monad · Applicative · Lens.
9. Patterns aren't memorized — they emerge as names
It's natural for the name to come to mind when, while writing code, a similar shape repeats and needs a label:
1) Write the code (without thinking about patterns)
2) The same shape shows up a second time → ask if there's a familiar name for it
3) If there is, apply the pattern. Communication with teammates speeds up
4) If there isn't, don't manufacture one. Simple code wins
Anti-flow — "let's apply 5 design patterns to this project" → the code usually gets more complex.
10. Common stumbles
Pattern fetish — wedging a Factory or Strategy where a simple function would do, doubling the code. Patterns are a burden for small code.
God Object — one class doing too much. An anti-pattern.
Anemic Domain Model — domain objects become data buckets while all behavior lives in services. Anti-pattern from a DDD perspective.
Spaghetti Code — flow tangled, untraceable. Pattern absent.
Singleton overuse — global state makes testing hard. Check whether dependency injection (DI) can replace it.
Same name, different intent — even "Adapter" can mean something different in your code than in GoF's definition. Same word doesn't mean same thing.
The myth that patterns answer everything — patterns are vocabulary. The view that good design uses few patterns is also common.
Closing thoughts
Design patterns aren't tools to memorize but vocabulary for communication. When the same shape appears a second time, naming it speeds up the discussion. In dynamic languages (JS, Python), more than half of the 23 patterns are absorbed by first-class functions and modules and become simpler. The myth that good design uses many patterns may run in reverse — KISS comes first.
Next
- oop-vs-functional
- (programming end)
We refer to Design Patterns: Elements of Reusable Object-Oriented Software (1994) · A Pattern Language (Christopher Alexander 1977) · Patterns of Enterprise Application Architecture (Martin Fowler 2002) · Design Patterns in Dynamic Languages (Peter Norvig 1996) · Refactoring (Martin Fowler) · Refactoring.Guru · Game Programming Patterns (Robert Nystrom) · Domain-Driven Design (Eric Evans 2003) · Anti-Patterns (Wikipedia).