Pattern Comparison Guide¶
This guide helps you choose between similar patterns by comparing their purposes, use cases, and differences.
Creational Pattern Comparisons¶
Factory vs Abstract Factory vs Builder¶
| Aspect | Factory | Abstract Factory | Builder |
|---|---|---|---|
| Purpose | Create single objects | Create families of objects | Construct complex objects |
| Complexity | Simple | Medium | High |
| When to Use | Single product type varies | Related products must work together | Many construction parameters |
| Example | Create different loggers | Create UI themes (button+checkbox+input) | Build computer with many specs |
Choose Factory when: - You need to create one type of object - Type determined at runtime - Simple creation logic
Choose Abstract Factory when: - Multiple related products must be created together - Products come in families (Windows UI, Mac UI) - System should be independent of product creation
Choose Builder when: - Object has many optional parameters - Construction requires multiple steps - You want immutability with flexible construction
# Factory: Create one thing
logger = LoggerFactory.create("file")
# Abstract Factory: Create family of related things
factory = WindowsFactory()
button = factory.create_button()
checkbox = factory.create_checkbox()
# Builder: Construct complex object step-by-step
computer = ComputerBuilder()
.set_cpu("Intel i9")
.set_ram(32)
.set_storage("1TB")
.build()
Singleton vs Dependency Injection¶
| Aspect | Singleton | Dependency Injection |
|---|---|---|
| Coupling | Tight (global state) | Loose |
| Testing | Difficult | Easy |
| Control | Pattern controls creation | Framework controls |
| Flexibility | Limited | High |
Prefer Dependency Injection when: - Testing is important - Multiple instances might be needed in tests - You want loose coupling
Use Singleton only when: - Truly need one instance system-wide - Testing complexity is acceptable - Global access is genuinely needed
# Singleton: Global state (harder to test)
config = Configuration() # Always same instance
# Dependency Injection: Explicit dependencies (easier to test)
class UserService:
def __init__(self, config: Configuration):
self.config = config
# Testing with DI is easier
def test_user_service():
test_config = Configuration()
service = UserService(test_config) # Use test config
Behavioral Pattern Comparisons¶
Strategy vs State vs Command¶
| Aspect | Strategy | State | Command |
|---|---|---|---|
| Purpose | Interchangeable algorithms | Behavior depends on state | Encapsulate requests |
| Changes | Algorithm | State | Operation |
| Transitions | No transitions | Has state transitions | No state |
| Undo | No | No | Yes |
Choose Strategy when: - Multiple algorithms for same task - Selection happens at runtime - Algorithms are independent
Choose State when: - Object behavior depends on its state - Many state transitions - State-specific behavior is complex
Choose Command when: - You need undo/redo - Operations should be queued/logged - Parameterize objects with operations
# Strategy: Pick algorithm at runtime
sorter = DataSorter(QuickSort())
sorter.sort(data)
sorter.set_strategy(MergeSort())
# State: Behavior changes with state
document.publish() # Draft -> Moderation
document.approve() # Moderation -> Published
# Command: Encapsulate operations for undo
cmd = SaveCommand(document)
cmd.execute()
cmd.undo() # Restore previous state
Observer vs Mediator vs Event Bus¶
| Aspect | Observer | Mediator | Event Bus |
|---|---|---|---|
| Communication | One-to-many | Many-to-many through mediator | Publish/Subscribe |
| Coupling | Subject knows observers | Objects know mediator | Complete decoupling |
| Complexity | Simple | Medium | Low |
Choose Observer when: - One object changes affect many others - Relationships are one-directional - Observers are independent
Choose Mediator when: - Complex communication patterns - Many objects interact - Want to centralize control logic
Choose Event Bus when: - Complete decoupling needed - Publishers don't know subscribers - Dynamic subscription needed
# Observer: Subject notifies observers
weather_station.attach(phone_display)
weather_station.attach(tv_display)
weather_station.set_temperature(25) # Both displays update
# Mediator: Central coordinator
chatroom = ChatRoom()
alice = User("Alice", chatroom)
bob = User("Bob", chatroom)
alice.send("Hi") # Goes through chatroom
# Event Bus: Publish/Subscribe
bus.subscribe("user_registered", send_email)
bus.publish("user_registered", user_data)
Template Method vs Strategy¶
| Aspect | Template Method | Strategy |
|---|---|---|
| Mechanism | Inheritance | Composition |
| Flexibility | Compile-time | Runtime |
| Coupling | Tighter | Looser |
| Change | Override methods | Swap objects |
Choose Template Method when: - Algorithm structure is fixed - Subclasses customize specific steps - Inheritance is appropriate
Choose Strategy when: - Algorithm can change at runtime - Want to avoid inheritance - Need algorithm objects to be reusable
# Template Method: Override specific steps
class PDFParser(DocumentParser):
def parse_header(self): # Customize step
return parse_pdf_header()
# Strategy: Swap entire algorithm
parser = DocumentParser()
parser.set_strategy(PDFParsingStrategy()) # Change at runtime
Visitor vs Iterator¶
| Aspect | Visitor | Iterator |
|---|---|---|
| Purpose | Add operations to structure | Traverse structure |
| Structure | Can be heterogeneous | Usually homogeneous |
| Operations | Many different operations | One operation (traversal) |
Choose Visitor when: - Many unrelated operations on objects - Object structure stable, operations change - Operations need different algorithms
Choose Iterator when: - Need to traverse collection - Hide internal structure - Support multiple traversals
# Visitor: Different operations on same structure
shapes.accept(AreaCalculator())
shapes.accept(XMLExporter())
shapes.accept(PerimeterCalculator())
# Iterator: Traverse collection
for shape in shapes: # Just iterate
process(shape)
Structural Pattern Comparisons¶
Adapter vs Facade vs Proxy¶
| Aspect | Adapter | Facade | Proxy |
|---|---|---|---|
| Purpose | Interface compatibility | Simplify interface | Control access |
| Relationship | Converts interface | Simplifies complex system | Same interface |
| When | After design | Complex subsystem | Need control layer |
Choose Adapter when: - Existing class has wrong interface - Want to use incompatible interfaces - Third-party integration needed
Choose Facade when: - Complex subsystem needs simple interface - Many dependencies exist - Want to layer subsystems
Choose Proxy when: - Need lazy initialization - Access control required - Want caching or logging
# Adapter: Make incompatible interfaces work
adapter = LoggerAdapter(legacy_logger)
adapter.log_info("message") # Adapts to new interface
# Facade: Simplify complex system
theater = HomeTheaterFacade()
theater.watch_movie("Inception") # Hides complexity
# Proxy: Control access to object
image = ImageProxy("large_image.jpg")
image.display() # Lazy loads only when needed
Decorator vs Proxy vs Adapter¶
| Aspect | Decorator | Proxy | Adapter |
|---|---|---|---|
| Purpose | Add behavior | Control access | Change interface |
| Interface | Same | Same | Different |
| Stacking | Yes | No | No |
| Focus | Enhancement | Control | Compatibility |
Choose Decorator when: - Add responsibilities dynamically - Stack multiple behaviors - Want flexible enhancement
Choose Proxy when: - Control object access - Lazy loading needed - Add indirection layer
Choose Adapter when: - Interfaces don't match - Want to reuse existing classes - Third-party integration
# Decorator: Stack behaviors
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee) # Stack multiple
# Proxy: Control access
image = ImageProxy("image.jpg") # Controls loading
if condition:
image.display() # Only loads if displayed
# Adapter: Convert interface
adapter = LoggerAdapter(old_logger) # Changes interface
adapter.log_info("msg") # New interface
Composite vs Decorator¶
| Aspect | Composite | Decorator |
|---|---|---|
| Purpose | Part-whole hierarchy | Add responsibilities |
| Structure | Tree | Linear |
| Focus | Structure | Behavior |
| Uniformity | Treat parts/wholes same | Enhance objects |
Choose Composite when: - Represent part-whole hierarchies - Want uniform treatment - Tree structure fits domain
Choose Decorator when: - Add behavior dynamically - Want to stack features - Avoid subclass explosion
# Composite: Tree structure
group = CompositeShape()
group.add(Circle())
group.add(Rectangle())
group.draw() # Draws all children
# Decorator: Linear wrapping
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee) # Wraps, not composes
Bridge vs Strategy¶
| Aspect | Bridge | Strategy |
|---|---|---|
| Purpose | Separate abstraction from implementation | Separate algorithms |
| Scope | Class level | Object level |
| Variability | Both abstraction and implementation | Algorithm only |
Choose Bridge when: - Both abstraction and implementation vary - Want to avoid class explosion - Compile-time separation needed
Choose Strategy when: - Only algorithm varies - Runtime selection needed - Simpler relationship
# Bridge: Abstraction and implementation vary independently
circle = Circle(VectorRenderer()) # Abstraction + Implementation
square = Square(RasterRenderer())
# Strategy: Just algorithm varies
sorter = Sorter(QuickSort()) # Just algorithm changes
Common Confusions¶
When NOT to use patterns:¶
- Don't use Factory if you only have one concrete class
- Don't use Singleton if you're just avoiding passing parameters
- Don't use Abstract Factory if products aren't related
- Don't use Builder for simple objects
- Don't use Observer for simple one-to-one relationships
- Don't use Mediator if direct communication is simpler
- Don't use Visitor if structure changes frequently
- Don't use Strategy if you only have one algorithm
- Don't use State for simple boolean flags
- Don't use Proxy if direct access is sufficient
Pattern Combinations:¶
Common combinations that work well together:
- Factory + Singleton: Singleton factory for object creation
- Observer + Mediator: Mediator coordinates observers
- Strategy + Factory: Factory creates strategies
- Composite + Visitor: Visit composite structures
- Decorator + Strategy: Decorate strategy objects
- Builder + Prototype: Builder creates prototype templates
- Command + Memento: Commands store mementos for undo
- Proxy + Decorator: Proxy with added behavior
- Facade + Singleton: Singleton facade for subsystem
- Iterator + Composite: Iterate composite structures
Decision Tree:¶
Need to create objects?
├─ Single object → Factory
├─ Families of objects → Abstract Factory
├─ Complex construction → Builder
├─ Clone expensive objects → Prototype
└─ One instance → Singleton
Need to control behavior?
├─ Multiple algorithms → Strategy
├─ State-dependent behavior → State
├─ Encapsulate requests → Command
├─ Chain handling → Chain of Responsibility
└─ Traverse collection → Iterator
Need to control structure?
├─ Add behavior dynamically → Decorator
├─ Interface compatibility → Adapter
├─ Simplify complex system → Facade
├─ Control access → Proxy
├─ Part-whole hierarchy → Composite
└─ Separate abstraction/implementation → Bridge
Need object communication?
├─ One-to-many updates → Observer
├─ Centralize interaction → Mediator
└─ Add operations → Visitor
Need to manage state?
├─ Undo/redo → Memento
└─ Algorithm skeleton → Template Method