Why This Matters
Design patterns are the vocabulary of professional software development—and they're exactly what separates code that works from code that's maintainable, extensible, and elegant. You're being tested not just on what each pattern does, but on when to apply it and what problem it solves. Understanding patterns means recognizing recurring design challenges: object creation complexity, interface incompatibility, behavioral flexibility, and structural organization.
Think of patterns as proven solutions to problems developers encounter repeatedly. The Gang of Four (GoF) organized these into three categories—creational, structural, and behavioral—and that classification tells you everything about a pattern's purpose. Don't just memorize definitions; know which principle each pattern embodies (single responsibility, open-closed, dependency inversion) and be ready to compare patterns that solve similar problems in different ways.
Creational Patterns: Controlling Object Instantiation
Creational patterns abstract the instantiation process, giving you flexibility in what gets created, who creates it, and how it's created. These patterns become essential when direct construction (using new) creates coupling or complexity you want to avoid.
Singleton Pattern
- Ensures exactly one instance of a class exists globally—critical for shared resources like configuration managers, logging services, or database connection pools
- Provides a global access point through a static method, typically
getInstance(), that returns the single instance
- Prevents inconsistent state that would occur if multiple instances competed for the same resource or stored conflicting data
Factory Method Pattern
- Defines an interface for object creation while letting subclasses decide which concrete class to instantiate—the "virtual constructor"
- Promotes loose coupling by eliminating hard-coded class names; client code works with abstractions, not implementations
- Enables extensibility since adding new product types requires only creating new subclasses, not modifying existing code
Abstract Factory Pattern
- Creates families of related objects without specifying concrete classes—think UI toolkits that produce matching buttons, scrollbars, and windows
- Enforces consistency across product families; you can't accidentally mix a Windows button with a Mac scrollbar
- Supports easy theme-switching by swapping the entire factory, making it ideal for cross-platform applications
Builder Pattern
- Separates construction from representation by extracting object creation into a dedicated builder class with step-by-step methods
- Handles complex objects with many optional parameters—instead of telescoping constructors, use
setName().setAge().setAddress().build()
- Improves readability by making construction code self-documenting and allowing different builders to create different representations
Prototype Pattern
- Creates objects by cloning an existing prototype rather than instantiating from scratch—copy, then customize
- Optimizes performance when object creation is expensive (database lookups, complex calculations, heavy initialization)
- Supports dynamic configuration since prototypes can be registered and retrieved at runtime without knowing concrete classes
Compare: Factory Method vs. Abstract Factory—both delegate object creation, but Factory Method uses inheritance (subclasses override a method) while Abstract Factory uses composition (inject a factory object). If an exam question involves creating families of related objects, Abstract Factory is your answer.
Compare: Builder vs. Prototype—Builder constructs objects step-by-step for complex initialization, while Prototype copies existing objects for performance optimization. Choose Builder when construction logic is complicated; choose Prototype when you need many similar objects quickly.
Structural Patterns: Composing Classes and Objects
Structural patterns focus on how classes and objects are composed to form larger structures. They use inheritance and composition to create flexible, efficient architectures that can adapt to changing requirements.
Adapter Pattern
- Converts incompatible interfaces by wrapping an existing class with a new interface that clients expect—the translator between old and new
- Enables legacy integration without modifying original source code, making it essential for working with third-party libraries
- Implements either through inheritance (class adapter) or composition (object adapter), with composition being more flexible
Decorator Pattern
- Adds responsibilities dynamically by wrapping objects in decorator classes that implement the same interface
- Provides a flexible alternative to subclassing—instead of creating
CoffeeWithMilk, CoffeeWithSugar, and CoffeeWithMilkAndSugar subclasses, stack decorators
- Adheres to Single Responsibility Principle by isolating each behavior extension in its own class
Composite Pattern
- Represents part-whole hierarchies as tree structures where individual objects and compositions share the same interface
- Enables uniform treatment of leaves and branches—client code calls
draw() without knowing if it's a single shape or a group
- Simplifies client code by eliminating type-checking logic; recursive operations propagate naturally through the tree
Facade Pattern
- Provides a simplified interface to a complex subsystem, hiding implementation details behind a clean API
- Reduces coupling between clients and subsystem classes—changes to the subsystem don't ripple through client code
- Improves usability by offering convenient default behaviors while still allowing direct subsystem access when needed
Proxy Pattern
- Controls access to another object by providing a surrogate that intercepts requests before forwarding them
- Implements lazy initialization (virtual proxy), access control (protection proxy), logging (logging proxy), or caching (caching proxy)
- Maintains the same interface as the real subject, making it transparent to clients—they don't know they're using a proxy
Compare: Adapter vs. Facade—Adapter makes one incompatible interface work with your code, while Facade simplifies access to an entire subsystem. Adapter changes an interface; Facade doesn't change anything, it just provides a convenient entry point.
Compare: Decorator vs. Proxy—both wrap objects, but Decorator adds behavior while Proxy controls access. Decorator typically allows stacking multiple wrappers; Proxy usually wraps exactly once for a specific purpose like security or lazy loading.
Behavioral Patterns: Managing Algorithms and Communication
Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They describe how objects interact and how responsibilities are distributed, often using delegation and composition to achieve flexibility.
Observer Pattern
- Establishes one-to-many dependencies so that when a subject changes state, all registered observers are notified automatically
- Enables event-driven architecture where publishers and subscribers are loosely coupled—subjects don't need to know observer types
- Supports broadcast communication essential for UI frameworks, real-time data feeds, and the Model-View-Controller pattern
Strategy Pattern
- Encapsulates interchangeable algorithms in separate classes, allowing runtime selection without conditional statements
- Eliminates switch/if-else chains by replacing them with polymorphism—each strategy implements a common interface
- Promotes Open-Closed Principle since new algorithms can be added without modifying existing code
Command Pattern
- Encapsulates requests as objects with a common interface (typically an
execute() method), decoupling sender from receiver
- Enables undo/redo functionality by storing command history and implementing
undo() methods
- Supports queuing and logging of operations, making it ideal for transaction systems and macro recording
State Pattern
- Allows behavior changes based on internal state by delegating state-specific behavior to separate state classes
- Eliminates complex conditional logic that would otherwise check state before every operation
- Makes state transitions explicit by encapsulating each state and its transitions in dedicated classes
Iterator Pattern
- Provides sequential access to collection elements without exposing the underlying data structure
- Supports multiple simultaneous traversals since each iterator maintains its own position independently
- Enables polymorphic iteration where the same client code can traverse arrays, linked lists, or trees uniformly
Compare: Strategy vs. State—both use delegation to change behavior, but Strategy lets clients choose algorithms while State changes behavior automatically based on internal conditions. Strategy is about what algorithm to use; State is about when behavior should change.
Compare: Observer vs. Command—Observer handles notifications (something happened, react to it) while Command handles requests (do this specific thing). Use Observer for reactive systems; use Command when you need to parameterize, queue, or undo operations.
Quick Reference Table
|
| Controlling instantiation | Singleton, Factory Method, Abstract Factory |
| Building complex objects | Builder, Prototype |
| Interface adaptation | Adapter, Facade |
| Adding behavior dynamically | Decorator, Proxy |
| Representing hierarchies | Composite |
| Encapsulating algorithms | Strategy, Command |
| Managing state and events | Observer, State |
| Traversing collections | Iterator |
| Loose coupling | Factory Method, Observer, Strategy, Command |
| Open-Closed Principle | Decorator, Strategy, Observer |
Self-Check Questions
-
Both Decorator and Proxy wrap objects—what's the key difference in their intent, and when would you choose each?
-
You're building a document editor that needs undo functionality. Which pattern is most appropriate, and what method would each command object need to implement?
-
Compare Factory Method and Abstract Factory: which uses inheritance for object creation, and which uses composition? When would you prefer one over the other?
-
A game needs different AI behaviors (aggressive, defensive, random) that can be swapped at runtime. Which pattern applies, and how does it eliminate conditional logic?
-
You're integrating a legacy payment system with a new e-commerce platform, and their interfaces don't match. Which structural pattern solves this, and how does it differ from Facade?