Skip to content

Decorator Pattern

Add behavior dynamically - wrap objects to extend functionality without modifying them!

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.

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.

The Decorator Pattern is useful when:

  1. You want to add behavior dynamically - At runtime, not compile time
  2. You want to add behavior to individual objects - Not entire classes
  3. You want to avoid subclass explosion - Too many combinations to subclass
  4. You want flexible extension - Add features without modifying existing code
  5. 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

Let’s start with a super simple example that anyone can understand!

Diagram

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!

You’re building a coffee shop system. You have different types of coffee and different add-ons (milk, sugar, whipped cream). Without Decorator Pattern:

bad_coffee_shop.py
# ❌ 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

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
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"
decorator_coffee_shop.py
from abc import ABC, abstractmethod
# Step 1: Define the component interface
class 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 component
class 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 decorator
class 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 decorators
class 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()

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.

You’re building a text editor. You need to apply formatting (bold, italic, underline) to text. Without Decorator Pattern:

bad_text_formatter.py
# ❌ 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!

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
decorator_text_formatter.py
from abc import ABC, abstractmethod
# Step 1: Define the component interface
class TextComponent(ABC):
"""Component interface - what text should do"""
@abstractmethod
def render(self) -> str:
"""Render the text"""
pass
# Step 2: Implement the concrete component
class 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 decorator
class 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 decorators
class 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()

There are different ways to implement the Decorator Pattern:

Decorator implements the same interface as component, client doesn’t know it’s decorated:

transparent_decorator.py
# Transparent Decorator - same interface
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()
# Client treats decorator same as component
component = ConcreteComponent()
decorated = 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

Decorator adds new methods, client needs to know it’s decorated:

opaque_decorator.py
# Opaque Decorator - adds new methods
class 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_operation
decorated = Decorator(component)
decorated.extra_operation() # Only works if client knows it's Decorator

Pros: Can add decorator-specific functionality
Cons: Client needs to know about decorators, breaks transparency


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

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


Mistake 1: Confusing Decorator with Adapter

Section titled “Mistake 1: Confusing Decorator with Adapter”
decorator_vs_adapter.py
# ❌ 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: Decorator
class 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: Adapter
class 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

Mistake 2: Not Delegating to Wrapped Component

Section titled “Mistake 2: Not Delegating to Wrapped Component”
no_delegation.py
# ❌ Bad: Not delegating to wrapped component
class Decorator:
def __init__(self, component):
self.component = component
def operation(self):
return "Decorated" # Bad: Doesn't use component!
# ✅ Good: Delegates to wrapped component
class 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

Mistake 3: Modifying Component Instead of Decorating

Section titled “Mistake 3: Modifying Component Instead of Decorating”
modifying_component.py
# ❌ Bad: Modifying component directly
class 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 decorator
class 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

  1. No Subclass Explosion - 1 base + N decorators = 1 + N classes (not 2^N)
  2. Dynamic Combination - Can combine decorators at runtime
  3. Flexible Extension - Add new decorators without modifying existing code
  4. Open/Closed Principle - Open for extension, closed for modification
  5. Single Responsibility - Each decorator adds one feature
  6. Runtime Behavior - Can add/remove features at runtime

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.

  • 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
  1. Define component interface - What the component should do
  2. Implement concrete component - Base object
  3. Create base decorator - Wraps component, implements same interface
  4. Implement concrete decorators - Each adds one feature
  5. Wrap dynamically - Combine decorators at runtime
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
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()})"

✅ Add behavior dynamically
✅ Avoid subclass explosion
✅ Combine features flexibly
✅ Extend without modifying code
✅ Add features at runtime

❌ Simple inheritance works
❌ Performance critical (adds indirection)
❌ Over-engineering simple cases
❌ Feature order significantly affects behavior

  • 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)
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})"
  • 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!

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

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.”

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.”

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

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.”

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!