Design Patterns
Practical GoF patterns for modern development.
Design Patterns
Based on the "Gang of Four" (GoF) book. Many of the original 23 patterns are obsolete in modern languages (like Iterator, which is now built-in), but several remain vital.
Creational Patterns
How objects are created.
Singleton
Ensures a class has only one instance.
Real-World Example: Database Connection Pool
- Problem: Multiple database connections waste resources
- Solution: Single connection pool shared across application
- Implementation: Private constructor, static instance, global access point
- Benefits: Resource efficiency, consistent state management
- Drawbacks: Testing difficulties, hidden dependencies, global state
Modern Alternatives:
- Use Dependency Injection containers
- Scoped services with controlled lifecycle
- Environment-specific configurations
Factory Method
Defines an interface for creating an object, but lets subclasses alter the type of objects that will be created.
Real-World Example: Payment Processing System
- PaymentFactory interface with
createPaymentProcessor()method - CreditCardFactory creates credit card processors
- PayPalFactory creates PayPal processors
- BankTransferFactory creates ACH processors
- Runtime Selection: Based on user payment method choice
Benefits:
- Decouples client from concrete classes
- Easy to add new payment types
- Follows Open/Closed Principle
- Centralized creation logic
When to Use:
- When you can't anticipate class types
- When subclass delegation is needed
- When you want to provide users flexibility
Abstract Factory
Provides an interface for creating families of related objects.
Real-World Example: UI Component Library
- ThemeFactory creates cohesive UI components
- DarkThemeFactory creates dark buttons, inputs, modals
- LightThemeFactory creates light variants
- Consistency: All components match the same theme
Benefits:
- Ensures product compatibility
- Isolates client from implementation details
- Easy to switch entire families
Structural Patterns
How objects are composed.
Adapter
Allows incompatible interfaces to work together.
Real-World Example: Payment Gateway Integration
- Problem: Stripe and PayPal have different APIs
- Solution: Create adapters that implement common interface
- StripeAdapter: Transforms calls to Stripe format
- PayPalAdapter: Transforms calls to PayPal format
- Client Code: Works with unified
PaymentGatewayinterface
Implementation Structure:
- Define
IPaymentGatewayinterface - Create adapters for each provider
- Client uses interface, not concrete classes
- Easy to add new payment providers
Benefits:
- Lets incompatible classes work together
- Improves reusability of existing code
- Decouples client from implementation
Facade
Provides a simplified interface to a complex library or framework.
Real-World Example: File Processing System
- Complex System: File parsers, validators, transformers, storage
- Facade:
FileProcessorwith singleprocessFile()method - Internal Steps: Parse → Validate → Transform → Store → Notify
- Client Experience: One method call handles everything
When to Use:
- Complex subsystems with many dependencies
- Need for simplified API
- Layer separation in architecture
Benefits:
- Hides complexity from clients
- Reduces coupling between subsystem and client
- Provides clear entry points
Decorator
Adds new functionality to objects dynamically without altering their structure.
Real-World Example: Coffee Ordering System
- Base: Simple coffee object
- Decorators: Milk, Sugar, Vanilla, Whipped cream
- Dynamic: Mix and match at runtime
- Pricing: Each decorator adds to base price
Benefits:
- More flexible than inheritance
- Add/remove responsibilities at runtime
- Follows Single Responsibility Principle
Proxy
Provides a surrogate or placeholder for another object to control access to it.
Real-World Example: Image Loading
- RealImage: Heavy object loads from disk
- ProxyImage: Lightweight placeholder
- Lazy Loading: Load real image only when displayed
- Additional: Caching, access control, logging
Proxy Types:
- Virtual proxies (lazy loading)
- Protection proxies (access control)
- Remote proxies (network access)
Behavioral Patterns
How objects communicate.
Strategy
Enables selecting an algorithm at runtime.
Real-World Example: Navigation App Routing
- Context: Navigation system
- Strategies: Fastest route, shortest distance, avoid tolls, scenic route
- Runtime Selection: User chooses preference
- Interchangeable: Easy to switch algorithms
Implementation Structure:
RouteStrategyinterface withcalculateRoute()method- Concrete strategies for each algorithm
- Context maintains strategy reference
- Client can change strategy at runtime
Benefits:
- Eliminates conditional statements
- Easier to add new algorithms
- Follows Open/Closed Principle
Observer (Pub/Sub)
One object notifies others of state changes.
Real-World Example: Stock Market Monitoring
- Subject: Stock price service
- Observers: Trading bots, alert systems, dashboard
- Events: Price changes, volume spikes, market news
- Independence: Observers operate autonomously
Use Cases:
- UI event handling (button clicks, form submissions)
- Real-time data feeds
- Notification systems
- Model-View-Controller patterns
Benefits:
- Loose coupling between subject and observers
- Dynamic subscription/unsubscription
- Broadcast communication
Command
Encapsulates a request as an object.
Real-World Example: Restaurant Ordering
- Command: Order object with all details
- Invoker: Waiter takes orders
- Receiver: Kitchen prepares food
- Features: Queue orders, undo/redo, order history
Benefits:
- Decouples invoker from receiver
- Supports undo/redo operations
- Can queue and log commands
- Easy to add new commands
Template Method
Defines the skeleton of an algorithm, letting subclasses fill in specific steps.
Real-World Example: Data Processing Pipeline
- Template:
processData()method with fixed steps - Steps: Validate → Transform → Save → Notify
- Variations: Different data formats implement steps differently
- Invariants: Overall algorithm structure unchanged
Benefits:
- Code reuse for invariant parts
- Flexibility for variable parts
- Control over algorithm structure
- Follows Hollywood Principle
Iterator
Provides a way to access elements of an aggregate object sequentially without exposing its underlying representation.
Real-World Example: Music Playlist
- Aggregate: Playlist collection
- Iterator: Traverses songs in order
- Variants: Shuffle, repeat, reverse
- Uniform: Same interface for different playlist types
Modern Relevance:
- Built into most languages (for...of loops)
- Still useful for custom collections
- Foundation for stream processing
Pattern Fever
"When you have a hammer, everything looks like a nail."
Engineers often learn a pattern and try to force it everywhere.
Modern Example: The overuse of useEffect in React.
Many developers use it to sync state manually (Observer pattern), causing "Effect Chains" and unnecessary re-renders. often, simple derived state (calculating variables during render) is the cleaner, more performant solution.