Decorator Pattern
Decorator Pattern: Adding Behavior Dynamically
Section titled “Decorator Pattern: Adding Behavior Dynamically”Now let’s explore the Decorator Pattern - a powerful structural design pattern that lets you add behavior to objects dynamically.
Why Decorator Pattern?
Section titled “Why Decorator Pattern?”Imagine you’re ordering a coffee. You start with a basic coffee, then add milk, then add sugar, then add whipped cream. Each addition modifies your coffee without changing the base coffee itself. The Decorator Pattern works the same way - you wrap objects with decorators to add functionality dynamically!
The Decorator Pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.
What’s the Use of Decorator Pattern?
Section titled “What’s the Use of Decorator Pattern?”The Decorator Pattern is useful when:
- You want to add behavior dynamically - At runtime, not compile time
- You want to add behavior to individual objects - Not entire classes
- You want to avoid subclass explosion - Too many combinations to subclass
- You want flexible extension - Add features without modifying existing code
- You want to combine features - Multiple decorators can be combined
What Happens If We Don’t Use Decorator Pattern?
Section titled “What Happens If We Don’t Use Decorator Pattern?”Without the Decorator Pattern, you might:
- Subclass explosion - Need a class for every combination (Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, etc.)
- Modify existing code - Need to change base classes to add features
- Rigid design - Can’t add features at runtime
- Violate Open/Closed Principle - Need to modify code to extend functionality
- Code duplication - Similar code repeated across subclasses
Simple Example: The Coffee Shop
Section titled “Simple Example: The Coffee Shop”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Decorator Pattern works in practice - showing how decorators wrap components:
sequenceDiagram
participant Client
participant MilkDecorator
participant SugarDecorator
participant SimpleCoffee as SimpleCoffee
Client->>MilkDecorator: get_cost()
activate MilkDecorator
MilkDecorator->>SugarDecorator: get_cost()
activate SugarDecorator
SugarDecorator->>SimpleCoffee: get_cost()
activate SimpleCoffee
SimpleCoffee-->>SugarDecorator: 5.0
deactivate SimpleCoffee
SugarDecorator->>SugarDecorator: Add sugar cost: 0.5
SugarDecorator-->>MilkDecorator: 5.5
deactivate SugarDecorator
MilkDecorator->>MilkDecorator: Add milk cost: 1.0
MilkDecorator-->>Client: 6.5
deactivate MilkDecorator
Note over Client,SimpleCoffee: Decorators wrap components<br/>and add functionality!
The Problem
Section titled “The Problem”You’re building a coffee shop system. You have different types of coffee and different add-ons (milk, sugar, whipped cream). Without Decorator Pattern:
Problems:
- Subclass explosion - Need a class for every combination (2^n classes for n features)
- Hard to extend - Adding new feature requires many new classes
- Violates Open/Closed Principle - Need to modify code to extend functionality
- Can’t combine features dynamically - Combinations are fixed at compile time
The Solution: Decorator Pattern
Section titled “The Solution: Decorator Pattern”Class Structure
Section titled “Class Structure”classDiagram
class Coffee {
<<interface>>
+get_cost() float
+get_description() string
}
class SimpleCoffee {
+get_cost() float
+get_description() string
}
class CoffeeDecorator {
<<abstract>>
-coffee: Coffee
+get_cost() float
+get_description() string
}
class MilkDecorator {
+get_cost() float
+get_description() string
}
class SugarDecorator {
+get_cost() float
+get_description() string
}
Coffee <|.. SimpleCoffee : implements
Coffee <|.. CoffeeDecorator : implements
CoffeeDecorator --> Coffee : wraps
CoffeeDecorator <|-- MilkDecorator : extends
CoffeeDecorator <|-- SugarDecorator : extends
note for Coffee "Component interface"
note for SimpleCoffee "Concrete component"
note for CoffeeDecorator "Base decorator"
note for MilkDecorator "Concrete decorator"
Real-World Software Example: Text Formatting System
Section titled “Real-World Software Example: Text Formatting System”Now let’s see a realistic software example - a text editor that needs to apply different formatting (bold, italic, underline) to text dynamically.
The Problem
Section titled “The Problem”You’re building a text editor. You need to apply formatting (bold, italic, underline) to text. Without Decorator Pattern:
Problems:
- Subclass explosion - Need a class for every combination
- Hard to extend - Adding new format requires many new classes
- Can’t combine dynamically - Combinations are fixed at compile time
The Solution: Decorator Pattern
Section titled “The Solution: Decorator Pattern”Decorator Pattern Variants
Section titled “Decorator Pattern Variants”There are different ways to implement the Decorator Pattern:
1. Transparent Decorator (Preferred)
Section titled “1. Transparent Decorator (Preferred)”Decorator implements the same interface as component, client doesn’t know it’s decorated:
Pros: Client doesn’t need to know about decorators, uniform interface
Cons: Can’t access decorator-specific methods
2. Opaque Decorator
Section titled “2. Opaque Decorator”Decorator adds new methods, client needs to know it’s decorated:
Pros: Can add decorator-specific functionality
Cons: Client needs to know about decorators, breaks transparency
When to Use Decorator Pattern?
Section titled “When to Use Decorator Pattern?”Use Decorator Pattern when:
✅ You want to add behavior dynamically - At runtime, not compile time
✅ You want to add behavior to individual objects - Not entire classes
✅ You want to avoid subclass explosion - Too many combinations to subclass
✅ You want flexible extension - Add features without modifying existing code
✅ You want to combine features - Multiple decorators can be combined
When NOT to Use Decorator Pattern?
Section titled “When NOT to Use Decorator Pattern?”Don’t use Decorator Pattern when:
❌ Simple inheritance works - If you only have a few combinations, inheritance might be simpler
❌ Performance critical - Decorator adds indirection (usually negligible)
❌ Over-engineering - Don’t add complexity for simple cases
❌ Feature order matters - If decorator order affects behavior significantly
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Confusing Decorator with Adapter
Section titled “Mistake 1: Confusing Decorator with Adapter”Mistake 2: Not Delegating to Wrapped Component
Section titled “Mistake 2: Not Delegating to Wrapped Component”Mistake 3: Modifying Component Instead of Decorating
Section titled “Mistake 3: Modifying Component Instead of Decorating”Benefits of Decorator Pattern
Section titled “Benefits of Decorator Pattern”- No Subclass Explosion - 1 base + N decorators = 1 + N classes (not 2^N)
- Dynamic Combination - Can combine decorators at runtime
- Flexible Extension - Add new decorators without modifying existing code
- Open/Closed Principle - Open for extension, closed for modification
- Single Responsibility - Each decorator adds one feature
- Runtime Behavior - Can add/remove features at runtime
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Decorator Pattern?
Section titled “What is Decorator Pattern?”Decorator Pattern is a structural design pattern that attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.
Why Use It?
Section titled “Why Use It?”- ✅ Avoid subclass explosion - 1 base + N decorators = 1 + N classes (not 2^N)
- ✅ Dynamic combination - Can combine decorators at runtime
- ✅ Flexible extension - Add features without modifying code
- ✅ Open/Closed Principle - Open for extension, closed for modification
- ✅ Runtime behavior - Add/remove features at runtime
How It Works?
Section titled “How It Works?”- Define component interface - What the component should do
- Implement concrete component - Base object
- Create base decorator - Wraps component, implements same interface
- Implement concrete decorators - Each adds one feature
- Wrap dynamically - Combine decorators at runtime
Key Components
Section titled “Key Components”Component (interface)├── ConcreteComponent (implements Component)└── Decorator (implements Component, wraps Component) ├── ConcreteDecorator1 (extends Decorator) └── ConcreteDecorator2 (extends Decorator)- Component - Interface for objects that can be decorated
- ConcreteComponent - Base object
- Decorator - Base decorator that wraps component
- ConcreteDecorator - Specific decorator that adds one feature
- Client - Uses decorated components
Simple Example
Section titled “Simple Example”class Component: def operation(self): pass
class ConcreteComponent(Component): def operation(self): return "Component"
class Decorator(Component): def __init__(self, component: Component): self.component = component
def operation(self): return self.component.operation()
class ConcreteDecorator(Decorator): def operation(self): return f"Decorated({self.component.operation()})"When to Use?
Section titled “When to Use?”✅ Add behavior dynamically
✅ Avoid subclass explosion
✅ Combine features flexibly
✅ Extend without modifying code
✅ Add features at runtime
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple inheritance works
❌ Performance critical (adds indirection)
❌ Over-engineering simple cases
❌ Feature order significantly affects behavior
Key Takeaways
Section titled “Key Takeaways”- Decorator Pattern = Adds behavior dynamically by wrapping
- Component = Interface for objects
- Decorator = Wraps component, adds behavior
- Benefit = No subclass explosion, dynamic combination
- Use Case = Many feature combinations (coffee add-ons, text formatting)
Common Pattern Structure
Section titled “Common Pattern Structure”class Component: def operation(self): pass
class ConcreteComponent(Component): def operation(self): return "Component"
class Decorator(Component): def __init__(self, component: Component): self.component = component
def operation(self): return self.component.operation()
class ConcreteDecorator(Decorator): def operation(self): result = self.component.operation() return f"Enhanced({result})"Remember
Section titled “Remember”- Decorator Pattern wraps components to add behavior
- It uses same interface as component
- Decorators can be combined dynamically
- It prevents subclass explosion (2^n → 1+n)
- It’s about enhancement, not compatibility!
Interview Focus: Decorator Pattern
Section titled “Interview Focus: Decorator Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Decorator Pattern is a structural design pattern that attaches additional responsibilities to an object dynamically. It wraps objects with decorators to add functionality without modifying the base object, providing a flexible alternative to subclassing.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of when to use it
- Indicates you can explain concepts clearly
2. When to Use Decorator Pattern
Section titled “2. When to Use Decorator Pattern”Must mention:
- ✅ Avoid subclass explosion - Too many combinations to subclass (2^n problem)
- ✅ Dynamic behavior - Want to add features at runtime
- ✅ Flexible combination - Want to combine features dynamically
- ✅ Extend without modifying - Don’t want to modify existing code
Example scenario to give:
“I’d use Decorator Pattern when building a coffee shop system. I have a base coffee and 5 add-ons (milk, sugar, whipped cream, caramel, chocolate). Without Decorator, I’d need 2^5 = 32 subclasses for all combinations. With Decorator, I need 1 base + 5 decorators = 6 classes, and I can combine them dynamically at runtime.”
3. Decorator vs Adapter Pattern
Section titled “3. Decorator vs Adapter Pattern”Must discuss:
- Decorator: Adds behavior, same interface, enhances functionality
- Adapter: Makes compatible, different interface, about compatibility
- Key difference: Decorator enhances, Adapter adapts
Example to give:
“Decorator Pattern wraps objects to add behavior while keeping the same interface. Adapter Pattern wraps objects to make incompatible interfaces compatible. Decorator is about enhancement, Adapter is about compatibility. Decorator adds features, Adapter changes interface.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- No subclass explosion - 1 + N instead of 2^N classes
- Dynamic combination - Can combine decorators at runtime
- Flexible extension - Add new decorators without modifying code
- Open/Closed Principle - Open for extension, closed for modification
- Single Responsibility - Each decorator adds one feature
Trade-offs to acknowledge:
- Complexity - Adds abstraction layer
- Performance - Multiple wrappers add indirection
- Over-engineering risk - Can be overkill for simple cases
- Order dependency - Decorator order can matter
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Decorator Pattern and Inheritance?”
A:
“Inheritance creates a static relationship at compile time - you can’t change behavior at runtime. Decorator Pattern creates a dynamic relationship at runtime - you can wrap objects with decorators to add behavior dynamically. Inheritance requires a class for each combination, Decorator allows runtime combination.”
Q: “How does Decorator Pattern relate to SOLID principles?”
A:
“Decorator Pattern supports the Open/Closed Principle - you can extend functionality (add new decorators) without modifying existing code. It supports Single Responsibility Principle - each decorator adds one feature. It supports Dependency Inversion Principle by depending on the Component abstraction rather than concrete implementations.”
Q: “Can decorators be removed after being added?”
A:
“Yes, decorators can be removed, but it requires careful design. You’d need to maintain references to the original component or use a different structure. However, the typical use case is to build up decorators rather than remove them. If you need frequent add/remove, consider a different pattern like Strategy.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Decorator Pattern clearly in one sentence
- Explain when to use it (with examples showing subclass explosion)
- Describe Decorator vs Adapter Pattern
- Implement Decorator Pattern from scratch
- Compare with other structural patterns (Adapter, Composite)
- List benefits and trade-offs
- Identify common mistakes (not delegating, modifying component)
- Give 2-3 real-world examples
- Connect to SOLID principles
- Discuss when NOT to use it
- Explain how decorators can be combined
Remember: Decorator Pattern is about adding behavior dynamically - wrap objects to enhance functionality without subclass explosion! ☕