Design Patterns
Design Patterns
Design patterns are proven solutions to recurring design problems. The Gang of Four (Gamma, Helm, Johnson, Vlissides, 1994) catalogued 23 patterns across three categories. They are not code to copy but vocabulary for communicating design intent and a starting point for thinking through structural problems.
Creational Patterns
Singleton: ensures one instance exists and provides global access. Often overused; globals make testing hard, introduce hidden coupling, and create thread-safety concerns. Prefer dependency injection over singletons.
Factory Method: a base class defines a creation interface (createProduct()), subclasses override to decide which class to instantiate. Enables extension without modifying the factory's client code.
Abstract Factory: a factory of factories; produces families of related objects (a Mac UI factory produces Mac buttons, Mac checkboxes, Mac dialogs). Swapping the factory swaps the entire family.
Builder: separates construction from representation. new QueryBuilder().select("name").from("users").where("age > 18").build() is clearer than a constructor with 10 parameters and avoids "telescoping constructor" anti-pattern.
Prototype: creates objects by cloning an existing instance. Useful when construction is expensive and most new objects are similar to existing ones (e.g., game entities with complex initialization).
Structural Patterns
Adapter: converts one interface to another. A legacy XML API adapted to a modern JSON interface; a third-party payment library adapted to your PaymentProcessor interface. Adapters enable integration without modifying either side.
Decorator: wraps objects to add behavior without subclassing. HTTP middleware chains are decorators: logging(caching(compression(handler))) — each layer adds behavior while calling through to the next. JavaScript class decorators (@Injectable, @Memoize) are the language-level version.
Composite: composes objects into tree structures to represent part-whole hierarchies. A UI component tree is composite: each component can be a leaf (button) or composite (panel containing buttons). Code treating leaf and composite uniformly is cleaner than code distinguishing them.
Proxy: provides a surrogate or placeholder. A lazy-loading proxy delays expensive object creation until first use. A caching proxy stores results. A protective proxy enforces access control. A remote proxy represents an object on a different machine (the basis of RPC).
Facade: provides a simplified interface to a complex subsystem. A PaymentFacade might coordinate authentication, fraud checking, charging, receipt generation, and audit logging behind a single pay(amount, card) method.
Behavioral Patterns
Observer / Event: subject maintains a list of observers; notifies them on state change. DOM addEventListener is Observer. RxJS Observables, Node.js EventEmitter, React's useState (re-renders are notifications to the UI). The foundational pattern for reactive programming.
Strategy: encapsulates a family of algorithms; select one at runtime. A Sorter with a SortStrategy interface can be configured with QuickSort or MergeSort without changing calling code. This is OCP in action: new strategies extend the system without modifying it.
Command: encapsulates a request as an object. This enables undo/redo (store executed commands in a stack), transaction log (persist commands), macro recording (replay sequences), and deferred execution (queue commands for later). Text editors, design tools, and financial systems use Command extensively.
Template Method: defines algorithm skeleton in a base class with abstract steps that subclasses implement. A DataProcessor base class defines: readData(), processData(), writeData() — the template. Subclasses provide concrete implementations of each step. Django's class-based views follow this pattern.
Iterator: provides sequential access to collection elements without exposing the collection's structure. JavaScript's for...of works with any object implementing the iterator protocol ([Symbol.iterator]). Generators naturally produce iterators.
Chain of Responsibility: passes a request along a chain of handlers; each either handles it or passes to the next. HTTP middleware (Express.js, ASP.NET middleware) is Chain of Responsibility. Request validation, authentication, logging, and routing are separate handlers in a chain.
Pattern Anti-Patterns
The risk with patterns is over-engineering. A function that selects an algorithm with a ternary doesn't need the Strategy pattern. A simple object doesn't need Builder. Pattern application should reduce complexity, not add layers for their own sake.
Pattern smell: if you're creating classes primarily to satisfy a pattern's structure rather than to model a real concept, reconsider. The goal is readable, maintainable code — patterns are means, not ends.
In modern JavaScript/TypeScript, many classical OOP patterns are unnecessary: first-class functions replace Strategy and Command (just pass the function), closures replace many uses of classes with state, and algebraic types with pattern matching handle Visitor more elegantly.