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:
# ❌ Without Decorator Pattern - Subclass explosion!
class Coffee: """Base coffee""" def get_cost(self) -> float: return 5.0
def get_description(self) -> str: return "Coffee"
class CoffeeWithMilk(Coffee): """Coffee with milk""" def get_cost(self) -> float: return super().get_cost() + 1.0
def get_description(self) -> str: return super().get_description() + ", Milk"
class CoffeeWithSugar(Coffee): """Coffee with sugar""" def get_cost(self) -> float: return super().get_cost() + 0.5
def get_description(self) -> str: return super().get_description() + ", Sugar"
class CoffeeWithMilkAndSugar(Coffee): """Coffee with milk and sugar""" def get_cost(self) -> float: return super().get_cost() + 1.0 + 0.5
def get_description(self) -> str: return super().get_description() + ", Milk, Sugar"
class CoffeeWithWhippedCream(Coffee): """Coffee with whipped cream""" def get_cost(self) -> float: return super().get_cost() + 2.0
def get_description(self) -> str: return super().get_description() + ", Whipped Cream"
# Problems:# - Need a class for every combination!# - CoffeeWithMilkAndSugarAndWhippedCream?# - CoffeeWithMilkAndWhippedCream?# - 2^3 = 8 classes for 3 add-ons!# - Violates Open/Closed Principle// ❌ Without Decorator Pattern - Subclass explosion!
public class Coffee { // Base coffee public double getCost() { return 5.0; }
public String getDescription() { return "Coffee"; }}
public class CoffeeWithMilk extends Coffee { // Coffee with milk @Override public double getCost() { return super.getCost() + 1.0; }
@Override public String getDescription() { return super.getDescription() + ", Milk"; }}
public class CoffeeWithSugar extends Coffee { // Coffee with sugar @Override public double getCost() { return super.getCost() + 0.5; }
@Override public String getDescription() { return super.getDescription() + ", Sugar"; }}
public class CoffeeWithMilkAndSugar extends Coffee { // Coffee with milk and sugar @Override public double getCost() { return super.getCost() + 1.0 + 0.5; }
@Override public String getDescription() { return super.getDescription() + ", Milk, Sugar"; }}
public class CoffeeWithWhippedCream extends Coffee { // Coffee with whipped cream @Override public double getCost() { return super.getCost() + 2.0; }
@Override public String getDescription() { return super.getDescription() + ", Whipped Cream"; }}
// Problems:// - Need a class for every combination!// - CoffeeWithMilkAndSugarAndWhippedCream?// - CoffeeWithMilkAndWhippedCream?// - 2^3 = 8 classes for 3 add-ons!// - Violates Open/Closed PrincipleProblems:
- 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"
from abc import ABC, abstractmethod
# Step 1: Define the component interfaceclass Coffee(ABC): """Component interface - what coffee should do"""
@abstractmethod def get_cost(self) -> float: """Get the cost of the coffee""" pass
@abstractmethod def get_description(self) -> str: """Get the description of the coffee""" pass
# Step 2: Implement the concrete componentclass SimpleCoffee(Coffee): """Concrete component - basic coffee"""
def get_cost(self) -> float: """Get base coffee cost""" return 5.0
def get_description(self) -> str: """Get base coffee description""" return "Coffee"
# Step 3: Create the base decoratorclass CoffeeDecorator(Coffee): """Base decorator - wraps a coffee component"""
def __init__(self, coffee: Coffee): self.coffee = coffee # Wraps the component
def get_cost(self) -> float: """Delegate to wrapped coffee""" return self.coffee.get_cost()
def get_description(self) -> str: """Delegate to wrapped coffee""" return self.coffee.get_description()
# Step 4: Implement concrete decoratorsclass MilkDecorator(CoffeeDecorator): """Concrete decorator - adds milk"""
def get_cost(self) -> float: """Add milk cost to wrapped coffee""" return self.coffee.get_cost() + 1.0
def get_description(self) -> str: """Add milk description to wrapped coffee""" return self.coffee.get_description() + ", Milk"
class SugarDecorator(CoffeeDecorator): """Concrete decorator - adds sugar"""
def get_cost(self) -> float: """Add sugar cost to wrapped coffee""" return self.coffee.get_cost() + 0.5
def get_description(self) -> str: """Add sugar description to wrapped coffee""" return self.coffee.get_description() + ", Sugar"
class WhippedCreamDecorator(CoffeeDecorator): """Concrete decorator - adds whipped cream"""
def get_cost(self) -> float: """Add whipped cream cost to wrapped coffee""" return self.coffee.get_cost() + 2.0
def get_description(self) -> str: """Add whipped cream description to wrapped coffee""" return self.coffee.get_description() + ", Whipped Cream"
# Usage - Clean and flexible!def main(): # Start with simple coffee coffee = SimpleCoffee() print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Add milk coffee = MilkDecorator(coffee) print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Add sugar coffee = SugarDecorator(coffee) print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Add whipped cream coffee = WhippedCreamDecorator(coffee) print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
print("\n✅ Decorator Pattern allows dynamic feature combination!")
# Can combine in any order! coffee2 = SimpleCoffee() coffee2 = SugarDecorator(coffee2) # Sugar first coffee2 = MilkDecorator(coffee2) # Then milk print(f"\nDifferent order: {coffee2.get_description()}: ${coffee2.get_cost():.2f}")
if __name__ == "__main__": main()// Step 1: Define the component interfaceinterface Coffee { // Component interface - what coffee should do double getCost(); String getDescription();}
// Step 2: Implement the concrete componentclass SimpleCoffee implements Coffee { // Concrete component - basic coffee @Override public double getCost() { // Get base coffee cost return 5.0; }
@Override public String getDescription() { // Get base coffee description return "Coffee"; }}
// Step 3: Create the base decoratorabstract class CoffeeDecorator implements Coffee { // Base decorator - wraps a coffee component protected Coffee coffee; // Wraps the component
public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
@Override public double getCost() { // Delegate to wrapped coffee return coffee.getCost(); }
@Override public String getDescription() { // Delegate to wrapped coffee return coffee.getDescription(); }}
// Step 4: Implement concrete decoratorsclass MilkDecorator extends CoffeeDecorator { // Concrete decorator - adds milk public MilkDecorator(Coffee coffee) { super(coffee); }
@Override public double getCost() { // Add milk cost to wrapped coffee return coffee.getCost() + 1.0; }
@Override public String getDescription() { // Add milk description to wrapped coffee return coffee.getDescription() + ", Milk"; }}
class SugarDecorator extends CoffeeDecorator { // Concrete decorator - adds sugar public SugarDecorator(Coffee coffee) { super(coffee); }
@Override public double getCost() { // Add sugar cost to wrapped coffee return coffee.getCost() + 0.5; }
@Override public String getDescription() { // Add sugar description to wrapped coffee return coffee.getDescription() + ", Sugar"; }}
class WhippedCreamDecorator extends CoffeeDecorator { // Concrete decorator - adds whipped cream public WhippedCreamDecorator(Coffee coffee) { super(coffee); }
@Override public double getCost() { // Add whipped cream cost to wrapped coffee return coffee.getCost() + 2.0; }
@Override public String getDescription() { // Add whipped cream description to wrapped coffee return coffee.getDescription() + ", Whipped Cream"; }}
// Usage - Clean and flexible!public class Main { public static void main(String[] args) { // Start with simple coffee Coffee coffee = new SimpleCoffee(); System.out.println(coffee.getDescription() + ": $" + coffee.getCost());
// Add milk coffee = new MilkDecorator(coffee); System.out.println(coffee.getDescription() + ": $" + coffee.getCost());
// Add sugar coffee = new SugarDecorator(coffee); System.out.println(coffee.getDescription() + ": $" + coffee.getCost());
// Add whipped cream coffee = new WhippedCreamDecorator(coffee); System.out.println(coffee.getDescription() + ": $" + coffee.getCost());
System.out.println("\n✅ Decorator Pattern allows dynamic feature combination!");
// Can combine in any order! Coffee coffee2 = new SimpleCoffee(); coffee2 = new SugarDecorator(coffee2); // Sugar first coffee2 = new MilkDecorator(coffee2); // Then milk System.out.println("\nDifferent order: " + coffee2.getDescription() + ": $" + coffee2.getCost()); }}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:
# ❌ Without Decorator Pattern - Subclass explosion!
class Text: """Base text""" def render(self) -> str: return "Hello World"
class BoldText(Text): """Bold text""" def render(self) -> str: return f"<b>{super().render()}</b>"
class ItalicText(Text): """Italic text""" def render(self) -> str: return f"<i>{super().render()}</i>"
class BoldItalicText(Text): """Bold and italic text""" def render(self) -> str: return f"<b><i>{super().render()}</i></b>"
class UnderlineText(Text): """Underlined text""" def render(self) -> str: return f"<u>{super().render()}</u>"
class BoldItalicUnderlineText(Text): """Bold, italic, and underlined text""" def render(self) -> str: return f"<b><i><u>{super().render()}</u></i></b>"
# Problems:# - Need a class for every combination!# - BoldUnderlineText?# - ItalicUnderlineText?# - 2^3 = 8 classes for 3 formats!// ❌ Without Decorator Pattern - Subclass explosion!
public class Text { // Base text public String render() { return "Hello World"; }}
public class BoldText extends Text { // Bold text @Override public String render() { return "<b>" + super.render() + "</b>"; }}
public class ItalicText extends Text { // Italic text @Override public String render() { return "<i>" + super.render() + "</i>"; }}
public class BoldItalicText extends Text { // Bold and italic text @Override public String render() { return "<b><i>" + super.render() + "</i></b>"; }}
public class UnderlineText extends Text { // Underlined text @Override public String render() { return "<u>" + super.render() + "</u>"; }}
public class BoldItalicUnderlineText extends Text { // Bold, italic, and underlined text @Override public String render() { return "<b><i><u>" + super.render() + "</u></i></b>"; }}
// Problems:// - Need a class for every combination!// - BoldUnderlineText?// - ItalicUnderlineText?// - 2^3 = 8 classes for 3 formats!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”from abc import ABC, abstractmethod
# Step 1: Define the component interfaceclass TextComponent(ABC): """Component interface - what text should do"""
@abstractmethod def render(self) -> str: """Render the text""" pass
# Step 2: Implement the concrete componentclass PlainText(TextComponent): """Concrete component - plain text"""
def __init__(self, content: str): self.content = content
def render(self) -> str: """Render plain text""" return self.content
# Step 3: Create the base decoratorclass TextDecorator(TextComponent): """Base decorator - wraps a text component"""
def __init__(self, text: TextComponent): self.text = text # Wraps the component
def render(self) -> str: """Delegate to wrapped text""" return self.text.render()
# Step 4: Implement concrete decoratorsclass BoldDecorator(TextDecorator): """Concrete decorator - adds bold formatting"""
def render(self) -> str: """Add bold tags to wrapped text""" return f"<b>{self.text.render()}</b>"
class ItalicDecorator(TextDecorator): """Concrete decorator - adds italic formatting"""
def render(self) -> str: """Add italic tags to wrapped text""" return f"<i>{self.text.render()}</i>"
class UnderlineDecorator(TextDecorator): """Concrete decorator - adds underline formatting"""
def render(self) -> str: """Add underline tags to wrapped text""" return f"<u>{self.text.render()}</u>"
# Usage - Clean and flexible!def main(): # Start with plain text text = PlainText("Hello World") print(f"Plain: {text.render()}")
# Add bold text = BoldDecorator(text) print(f"Bold: {text.render()}")
# Add italic text = ItalicDecorator(text) print(f"Bold + Italic: {text.render()}")
# Add underline text = UnderlineDecorator(text) print(f"Bold + Italic + Underline: {text.render()}")
print("\n✅ Decorator Pattern allows dynamic formatting combination!")
# Can combine in any order! text2 = PlainText("Python") text2 = ItalicDecorator(text2) # Italic first text2 = BoldDecorator(text2) # Then bold print(f"\nDifferent order: {text2.render()}")
if __name__ == "__main__": main()// Step 1: Define the component interfaceinterface TextComponent { // Component interface - what text should do String render();}
// Step 2: Implement the concrete componentclass PlainText implements TextComponent { // Concrete component - plain text private String content;
public PlainText(String content) { this.content = content; }
@Override public String render() { // Render plain text return content; }}
// Step 3: Create the base decoratorabstract class TextDecorator implements TextComponent { // Base decorator - wraps a text component protected TextComponent text; // Wraps the component
public TextDecorator(TextComponent text) { this.text = text; }
@Override public String render() { // Delegate to wrapped text return text.render(); }}
// Step 4: Implement concrete decoratorsclass BoldDecorator extends TextDecorator { // Concrete decorator - adds bold formatting public BoldDecorator(TextComponent text) { super(text); }
@Override public String render() { // Add bold tags to wrapped text return "<b>" + text.render() + "</b>"; }}
class ItalicDecorator extends TextDecorator { // Concrete decorator - adds italic formatting public ItalicDecorator(TextComponent text) { super(text); }
@Override public String render() { // Add italic tags to wrapped text return "<i>" + text.render() + "</i>"; }}
class UnderlineDecorator extends TextDecorator { // Concrete decorator - adds underline formatting public UnderlineDecorator(TextComponent text) { super(text); }
@Override public String render() { // Add underline tags to wrapped text return "<u>" + text.render() + "</u>"; }}
// Usage - Clean and flexible!public class Main { public static void main(String[] args) { // Start with plain text TextComponent text = new PlainText("Hello World"); System.out.println("Plain: " + text.render());
// Add bold text = new BoldDecorator(text); System.out.println("Bold: " + text.render());
// Add italic text = new ItalicDecorator(text); System.out.println("Bold + Italic: " + text.render());
// Add underline text = new UnderlineDecorator(text); System.out.println("Bold + Italic + Underline: " + text.render());
System.out.println("\n✅ Decorator Pattern allows dynamic formatting combination!");
// Can combine in any order! TextComponent text2 = new PlainText("Python"); text2 = new ItalicDecorator(text2); // Italic first text2 = new BoldDecorator(text2); // Then bold System.out.println("\nDifferent order: " + text2.render()); }}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:
# Transparent Decorator - same interfaceclass 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()
# Client treats decorator same as componentcomponent = ConcreteComponent()decorated = Decorator(component)# Both have same interface - transparent!// Transparent Decorator - same interfaceinterface Component { String operation();}
class ConcreteComponent implements Component { @Override public String operation() { return "Component"; }}
class Decorator implements Component { protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public String operation() { return component.operation(); }}
// Client treats decorator same as componentComponent component = new ConcreteComponent();Component decorated = new Decorator(component);// Both have same interface - transparent!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:
# Opaque Decorator - adds new methodsclass Component: def operation(self): pass
class Decorator(Component): def __init__(self, component: Component): self.component = component
def operation(self): return self.component.operation()
def extra_operation(self): # New method return "Extra"
# Client needs to know it's decorated to use extra_operationdecorated = Decorator(component)decorated.extra_operation() # Only works if client knows it's Decorator// Opaque Decorator - adds new methodsinterface Component { String operation();}
class Decorator implements Component { protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public String operation() { return component.operation(); }
public String extraOperation() { // New method return "Extra"; }}
// Client needs to know it's decorated to use extraOperationDecorator decorated = new Decorator(component);decorated.extraOperation(); // Only works if client knows it's DecoratorPros: 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”# ❌ Confusing Decorator with Adapter
# Decorator Pattern: Adds behavior dynamically# - Wraps object to add functionality# - Same interface, enhanced behavior# - Can combine multiple decorators
# Adapter Pattern: Makes incompatible interfaces compatible# - Adapts interface to work together# - Different interface, same functionality# - About compatibility, not enhancement
# Example: Decoratorclass Coffee: def get_cost(self): return 5.0
class MilkDecorator(Coffee): def __init__(self, coffee: Coffee): self.coffee = coffee # Wraps to add behavior
def get_cost(self): return self.coffee.get_cost() + 1.0 # Adds functionality
# Example: Adapterclass OldInterface: def old_method(self): return "old"
class Adapter: def __init__(self, old: OldInterface): self.old = old # Adapts to make compatible
def new_method(self): # Different interface! return self.old.old_method() # Makes compatible// ❌ Confusing Decorator with Adapter
// Decorator Pattern: Adds behavior dynamically// - Wraps object to add functionality// - Same interface, enhanced behavior// - Can combine multiple decorators
// Adapter Pattern: Makes incompatible interfaces compatible// - Adapts interface to work together// - Different interface, same functionality// - About compatibility, not enhancement
// Example: Decoratorinterface Coffee { double getCost();}
class MilkDecorator implements Coffee { private Coffee coffee; // Wraps to add behavior
public MilkDecorator(Coffee coffee) { this.coffee = coffee; }
@Override public double getCost() { return coffee.getCost() + 1.0; // Adds functionality }}
// Example: Adapterclass OldInterface { public String oldMethod() { return "old"; }}
class Adapter { private OldInterface old; // Adapts to make compatible
public Adapter(OldInterface old) { this.old = old; }
public String newMethod() { // Different interface! return old.oldMethod(); // Makes compatible }}Mistake 2: Not Delegating to Wrapped Component
Section titled “Mistake 2: Not Delegating to Wrapped Component”# ❌ Bad: Not delegating to wrapped componentclass Decorator: def __init__(self, component): self.component = component
def operation(self): return "Decorated" # Bad: Doesn't use component!
# ✅ Good: Delegates to wrapped componentclass Decorator: def __init__(self, component): self.component = component
def operation(self): result = self.component.operation() # Good: Delegates first return f"Decorated({result})" # Then adds behavior// ❌ Bad: Not delegating to wrapped componentclass Decorator implements Component { protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public String operation() { return "Decorated"; // Bad: Doesn't use component! }}
// ✅ Good: Delegates to wrapped componentclass Decorator implements Component { protected Component component;
public Decorator(Component component) { this.component = component; }
@Override public String operation() { String result = component.operation(); // Good: Delegates first return "Decorated(" + result + ")"; // Then adds behavior }}Mistake 3: Modifying Component Instead of Decorating
Section titled “Mistake 3: Modifying Component Instead of Decorating”# ❌ Bad: Modifying component directlyclass Component: def __init__(self): self.features = [] # Bad: Storing features in component
def add_feature(self, feature): self.features.append(feature) # Bad: Modifying component
# ✅ Good: Using decoratorclass Component: def operation(self): return "Component"
class Decorator(Component): def __init__(self, component): self.component = component # Good: Wraps component
def operation(self): return self.component.operation() + " + Feature" # Good: Adds behavior// ❌ Bad: Modifying component directlyclass Component { private List<String> features = new ArrayList<>(); // Bad: Storing features in component
public void addFeature(String feature) { features.add(feature); // Bad: Modifying component }}
// ✅ Good: Using decoratorinterface Component { String operation();}
class Decorator implements Component { protected Component component; // Good: Wraps component
public Decorator(Component component) { this.component = component; }
@Override public String operation() { return component.operation() + " + Feature"; // Good: Adds behavior }}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! ☕