Decorator Pattern: Add Behavior Dynamically

Updated 2026-03-11

TL;DR

The Decorator Pattern lets you add new responsibilities to objects dynamically without modifying their code. Instead of using inheritance to extend functionality, you wrap objects in decorator classes that add behavior while maintaining the same interface. This provides a flexible alternative to subclassing for extending functionality.

Prerequisites: Understanding of classes and objects, inheritance and polymorphism, interfaces (or abstract base classes in Python), and basic composition concepts. Familiarity with the Open/Closed Principle (open for extension, closed for modification) is helpful.

After this topic: Implement the Decorator Pattern to add responsibilities to objects dynamically, identify situations where decoration is preferable to inheritance, design flexible systems that can combine multiple behaviors at runtime, and explain the trade-offs between decorators and subclassing.

Core Concept

What is the Decorator Pattern?

The Decorator Pattern is a structural design pattern that allows you to attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Instead of creating a complex inheritance hierarchy with every possible combination of features, you create small decorator classes that each add one specific behavior. You can then wrap an object in multiple decorators, stacking behaviors like layers.

Why Use Decorators?

Imagine a coffee shop system. You have a base Coffee class, but customers can add milk, sugar, whipped cream, or caramel. Creating a subclass for every combination (CoffeeWithMilk, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndWhippedCream) quickly becomes unmanageable — you’d need dozens of classes.

With decorators, you create one decorator for each add-on. You can then combine them at runtime: wrap a Coffee in a MilkDecorator, wrap that in a SugarDecorator, and so on. Each decorator adds its cost and description to the wrapped object.

Key Components

  1. Component Interface: Defines the interface that both concrete components and decorators implement
  2. Concrete Component: The base object that can be decorated
  3. Decorator Base Class: Implements the component interface and contains a reference to a component
  4. Concrete Decorators: Extend the decorator base class and add specific responsibilities

How It Works

Each decorator wraps another component (which could be the base object or another decorator). When a method is called on a decorator, it typically:

  1. Calls the same method on the wrapped component
  2. Adds its own behavior before or after that call
  3. Returns the combined result

This creates a chain of responsibility where each decorator adds its layer of functionality.

When to Use Decorators

  • You need to add responsibilities to individual objects, not entire classes
  • You want to add or remove responsibilities dynamically at runtime
  • Extension by subclassing is impractical (too many combinations)
  • You want to follow the Single Responsibility Principle (each decorator does one thing)

Visual Guide

Decorator Pattern Structure

classDiagram
    class Component {
        <<interface>>
        +operation()
    }
    class ConcreteComponent {
        +operation()
    }
    class Decorator {
        -component: Component
        +operation()
    }
    class ConcreteDecoratorA {
        +operation()
        +addedBehavior()
    }
    class ConcreteDecoratorB {
        +operation()
    }
    
    Component <|.. ConcreteComponent
    Component <|.. Decorator
    Component --o Decorator
    Decorator <|-- ConcreteDecoratorA
    Decorator <|-- ConcreteDecoratorB

The Decorator wraps a Component and implements the same interface. Concrete decorators extend the base Decorator to add specific behaviors.

Coffee Shop Example Flow

graph LR
    A[SimpleCoffee] --> B[MilkDecorator wraps SimpleCoffee]
    B --> C[SugarDecorator wraps MilkDecorator]
    C --> D[WhippedCreamDecorator wraps SugarDecorator]
    D --> E[Final: Coffee + Milk + Sugar + Whipped Cream]

Each decorator wraps the previous component, creating a chain that combines all behaviors.

Examples

Example 1: Coffee Shop System

Let’s build a coffee ordering system where customers can customize their drinks with various add-ons.

from abc import ABC, abstractmethod

# Component Interface
class Coffee(ABC):
    @abstractmethod
    def cost(self) -> float:
        pass
    
    @abstractmethod
    def description(self) -> str:
        pass

# Concrete Component
class SimpleCoffee(Coffee):
    def cost(self) -> float:
        return 2.0
    
    def description(self) -> str:
        return "Simple coffee"

# Decorator Base Class
class CoffeeDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee
    
    def cost(self) -> float:
        return self._coffee.cost()
    
    def description(self) -> str:
        return self._coffee.description()

# Concrete Decorators
class MilkDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.5
    
    def description(self) -> str:
        return self._coffee.description() + ", milk"

class SugarDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.2
    
    def description(self) -> str:
        return self._coffee.description() + ", sugar"

class WhippedCreamDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.7
    
    def description(self) -> str:
        return self._coffee.description() + ", whipped cream"

# Usage
if __name__ == "__main__":
    # Simple coffee
    coffee = SimpleCoffee()
    print(f"{coffee.description()}: ${coffee.cost():.2f}")
    # Output: Simple coffee: $2.00
    
    # Coffee with milk
    coffee_with_milk = MilkDecorator(SimpleCoffee())
    print(f"{coffee_with_milk.description()}: ${coffee_with_milk.cost():.2f}")
    # Output: Simple coffee, milk: $2.50
    
    # Coffee with milk and sugar
    coffee_deluxe = SugarDecorator(MilkDecorator(SimpleCoffee()))
    print(f"{coffee_deluxe.description()}: ${coffee_deluxe.cost():.2f}")
    # Output: Simple coffee, milk, sugar: $2.70
    
    # Full customization
    fancy_coffee = WhippedCreamDecorator(
        SugarDecorator(
            MilkDecorator(SimpleCoffee())
        )
    )
    print(f"{fancy_coffee.description()}: ${fancy_coffee.cost():.2f}")
    # Output: Simple coffee, milk, sugar, whipped cream: $3.40

Key Points:

  • Each decorator wraps a Coffee object and adds its own cost and description
  • You can stack decorators in any order and combination
  • The client code doesn’t need to know about the decoration — it just calls cost() and description()
  • Adding a new add-on requires only one new decorator class

Try it yourself: Add a CaramelDecorator that costs $0.6 and a VanillaDecorator that costs $0.4. Create a coffee with all possible add-ons.

Example 2: Text Formatting System

Let’s create a system that can apply multiple formatting options to text dynamically.

from abc import ABC, abstractmethod

# Component Interface
class TextComponent(ABC):
    @abstractmethod
    def render(self) -> str:
        pass

# Concrete Component
class PlainText(TextComponent):
    def __init__(self, text: str):
        self._text = text
    
    def render(self) -> str:
        return self._text

# Decorator Base Class
class TextDecorator(TextComponent):
    def __init__(self, component: TextComponent):
        self._component = component
    
    def render(self) -> str:
        return self._component.render()

# Concrete Decorators
class BoldDecorator(TextDecorator):
    def render(self) -> str:
        return f"<b>{self._component.render()}</b>"

class ItalicDecorator(TextDecorator):
    def render(self) -> str:
        return f"<i>{self._component.render()}</i>"

class UnderlineDecorator(TextDecorator):
    def render(self) -> str:
        return f"<u>{self._component.render()}</u>"

class UpperCaseDecorator(TextDecorator):
    def render(self) -> str:
        return self._component.render().upper()

# Usage
if __name__ == "__main__":
    # Plain text
    text = PlainText("Hello, World!")
    print(text.render())
    # Output: Hello, World!
    
    # Bold text
    bold_text = BoldDecorator(PlainText("Hello, World!"))
    print(bold_text.render())
    # Output: <b>Hello, World!</b>
    
    # Bold and italic
    styled_text = ItalicDecorator(BoldDecorator(PlainText("Hello, World!")))
    print(styled_text.render())
    # Output: <i><b>Hello, World!</b></i>
    
    # All formatting options
    fancy_text = UnderlineDecorator(
        ItalicDecorator(
            BoldDecorator(
                UpperCaseDecorator(
                    PlainText("Hello, World!")
                )
            )
        )
    )
    print(fancy_text.render())
    # Output: <u><i><b>HELLO, WORLD!</b></i></u>
    
    # Order matters!
    different_order = UpperCaseDecorator(
        BoldDecorator(
            PlainText("Hello, World!")
        )
    )
    print(different_order.render())
    # Output: <B>HELLO, WORLD!</B> (tags are also uppercased)

Key Points:

  • The order of decorators matters — different orders produce different results
  • Each decorator is independent and can be combined with any other
  • The base PlainText object is never modified — decorators wrap it
  • This is more flexible than creating classes like BoldItalicText, BoldUnderlineText, etc.

Java/C++ Notes:

  • In Java, you’d use interfaces instead of abstract base classes: interface Coffee { double cost(); String description(); }
  • In C++, you’d use pure virtual functions: virtual double cost() = 0;
  • Java and C++ require explicit memory management for wrapped objects (especially C++ with pointers/smart pointers)

Try it yourself: Create a ColorDecorator that takes a color parameter and wraps text in <span style="color: {color}">. Combine it with other decorators.

Example 3: Data Stream Processing

A practical example showing how decorators can add compression and encryption to data streams.

from abc import ABC, abstractmethod
import base64
import zlib

# Component Interface
class DataSource(ABC):
    @abstractmethod
    def write(self, data: str) -> None:
        pass
    
    @abstractmethod
    def read(self) -> str:
        pass

# Concrete Component
class FileDataSource(DataSource):
    def __init__(self, filename: str):
        self._filename = filename
        self._data = ""
    
    def write(self, data: str) -> None:
        self._data = data
        print(f"Writing to {self._filename}: {data[:50]}...")
    
    def read(self) -> str:
        print(f"Reading from {self._filename}")
        return self._data

# Decorator Base Class
class DataSourceDecorator(DataSource):
    def __init__(self, source: DataSource):
        self._source = source
    
    def write(self, data: str) -> None:
        self._source.write(data)
    
    def read(self) -> str:
        return self._source.read()

# Concrete Decorators
class CompressionDecorator(DataSourceDecorator):
    def write(self, data: str) -> None:
        compressed = zlib.compress(data.encode())
        print(f"Compressing data: {len(data)} -> {len(compressed)} bytes")
        # Convert to base64 for string representation
        compressed_str = base64.b64encode(compressed).decode()
        self._source.write(compressed_str)
    
    def read(self) -> str:
        compressed_str = self._source.read()
        compressed = base64.b64decode(compressed_str.encode())
        decompressed = zlib.decompress(compressed).decode()
        print(f"Decompressing data: {len(compressed)} -> {len(decompressed)} bytes")
        return decompressed

class EncryptionDecorator(DataSourceDecorator):
    def __init__(self, source: DataSource, key: int = 13):
        super().__init__(source)
        self._key = key
    
    def write(self, data: str) -> None:
        encrypted = self._encrypt(data)
        print(f"Encrypting data with key {self._key}")
        self._source.write(encrypted)
    
    def read(self) -> str:
        encrypted = self._source.read()
        decrypted = self._decrypt(encrypted)
        print(f"Decrypting data with key {self._key}")
        return decrypted
    
    def _encrypt(self, data: str) -> str:
        # Simple Caesar cipher for demonstration
        return ''.join(chr((ord(char) + self._key) % 256) for char in data)
    
    def _decrypt(self, data: str) -> str:
        return ''.join(chr((ord(char) - self._key) % 256) for char in data)

# Usage
if __name__ == "__main__":
    # Plain file writing
    print("=== Plain File ===")
    plain = FileDataSource("data.txt")
    plain.write("Hello, World! This is a test message.")
    print(f"Read: {plain.read()}")
    print()
    
    # With compression
    print("=== With Compression ===")
    compressed = CompressionDecorator(FileDataSource("data.txt"))
    compressed.write("Hello, World! This is a test message.")
    print(f"Read: {compressed.read()}")
    print()
    
    # With encryption and compression
    print("=== With Encryption and Compression ===")
    secure = EncryptionDecorator(
        CompressionDecorator(
            FileDataSource("data.txt")
        )
    )
    secure.write("Hello, World! This is a test message.")
    print(f"Read: {secure.read()}")
    print()

# Output:
# === Plain File ===
# Writing to data.txt: Hello, World! This is a test message....
# Reading from data.txt
# Read: Hello, World! This is a test message.
#
# === With Compression ===
# Compressing data: 38 -> 46 bytes
# Writing to data.txt: eJzLSM3JyVcozy/KSVEEABKJBXw=...
# Reading from data.txt
# Decompressing data: 46 -> 38 bytes
# Read: Hello, World! This is a test message.
#
# === With Encryption and Compression ===
# Compressing data: 38 -> 46 bytes
# Encrypting data with key 13
# Writing to data.txt: rW{8Vy{rYVxrYV~rYV{rYV|rYV}...
# Reading from data.txt
# Decrypting data with key 13
# Decompressing data: 46 -> 38 bytes
# Read: Hello, World! This is a test message.

Key Points:

  • Decorators can transform data as it passes through the chain
  • The order matters: encrypt-then-compress vs compress-then-encrypt produces different results
  • Each decorator is responsible for both encoding (write) and decoding (read)
  • The base FileDataSource doesn’t know about compression or encryption

Try it yourself: Add a LoggingDecorator that prints timestamps and operation details before delegating to the wrapped component.

Common Mistakes

1. Forgetting to Delegate to the Wrapped Component

Mistake: Implementing decorator methods without calling the wrapped component’s methods.

class BadDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return 0.5  # Wrong! Doesn't include base cost

Why it’s wrong: The decorator replaces the base functionality instead of extending it. The whole point is to add to existing behavior.

Correct approach:

class GoodDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.5  # Adds to base cost

2. Creating Too Many Decorator Layers

Mistake: Wrapping objects in excessive layers of decorators, making code hard to read and debug.

# Hard to read and maintain
coffee = CaramelDecorator(
    WhippedCreamDecorator(
        SugarDecorator(
            MilkDecorator(
                VanillaDecorator(
                    SimpleCoffee()
                )
            )
        )
    )
)

Why it’s wrong: Deep nesting makes code unreadable and debugging difficult. If there’s an error, which decorator caused it?

Better approach: Use a builder pattern or factory to construct decorated objects, or consider if you really need that many decorators.

class CoffeeBuilder:
    def __init__(self):
        self._coffee = SimpleCoffee()
    
    def add_milk(self):
        self._coffee = MilkDecorator(self._coffee)
        return self
    
    def add_sugar(self):
        self._coffee = SugarDecorator(self._coffee)
        return self
    
    def build(self):
        return self._coffee

# Much more readable
coffee = CoffeeBuilder().add_milk().add_sugar().build()

3. Not Maintaining the Same Interface

Mistake: Adding methods to decorators that aren’t in the base component interface.

class BadMilkDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.5
    
    def get_milk_type(self) -> str:  # New method not in Coffee interface
        return "whole milk"

Why it’s wrong: Clients can’t use the new method polymorphically. If you have a Coffee reference, you can’t call get_milk_type() without type checking.

When it’s acceptable: If you’re certain clients will know the concrete decorator type and need the extra functionality. But this breaks the pattern’s intent.

4. Using Decorators When Inheritance Would Be Simpler

Mistake: Applying the Decorator Pattern when you have a fixed, small number of variations that won’t change.

Example: If you only ever have “Regular Coffee” and “Decaf Coffee” with no customization, two simple subclasses are clearer than decorators.

Why it’s wrong: Overengineering. Decorators add complexity — use them when you need runtime flexibility and multiple combinations.

Rule of thumb: Use decorators when you have multiple orthogonal features that can be combined in many ways. Use inheritance when you have a few distinct, fixed types.

5. Incorrect Decorator Order Assumptions

Mistake: Assuming decorators are order-independent when they’re not.

# These produce different results!
text1 = UpperCaseDecorator(BoldDecorator(PlainText("hello")))
print(text1.render())  # <B>HELLO</B> (tags uppercased)

text2 = BoldDecorator(UpperCaseDecorator(PlainText("hello")))
print(text2.render())  # <b>HELLO</b> (tags not uppercased)

Why it matters: The order of decorators affects the final result. Always document the expected order or make decorators order-independent when possible.

Best practice: Write unit tests for different decorator orders to ensure they behave as expected.

Interview Tips

What Interviewers Look For

1. Can you explain when to use Decorator vs. Inheritance?

Be ready to articulate: “Use decorators when you need to add responsibilities dynamically at runtime, or when you have many possible combinations of features. Use inheritance when you have a fixed set of types known at compile time. Decorators favor composition over inheritance and follow the Open/Closed Principle.”

2. Implement a decorator system from scratch

Interviewers often ask you to design a system like the coffee shop example. Practice implementing:

  • The component interface
  • A concrete component
  • The decorator base class
  • At least two concrete decorators
  • Client code that uses them

Time yourself — you should be able to write a basic decorator system in 10-15 minutes.

3. Identify real-world examples

Be prepared to discuss real-world uses:

  • Java I/O streams: BufferedReader(new FileReader("file.txt")) — each wrapper adds functionality
  • Python decorators: The @ syntax is related but not the same pattern (it’s more like function wrappers)
  • GUI components: Adding scrollbars, borders, or shadows to windows
  • Middleware in web frameworks: Each middleware wraps the request/response handling

4. Discuss trade-offs

Interviewers want to know you understand the downsides:

  • Complexity: More objects to manage, harder to debug
  • Identity issues: A decorated object isn’t the same type as the base object (matters for isinstance checks)
  • Order dependency: Decorator order can affect behavior
  • Performance: Extra method calls through the chain

Always mention: “I’d use decorators when flexibility is more important than simplicity.”

Common Interview Questions

Q: How is the Decorator Pattern different from the Proxy Pattern?

A: Both wrap an object, but their intent differs. A decorator adds responsibilities, while a proxy controls access. A proxy might add lazy loading, access control, or remote communication, but doesn’t fundamentally change what the object does. A decorator changes or enhances behavior.

Q: Can you have a decorator that wraps multiple components?

A: Typically no — decorators wrap a single component to maintain the same interface. If you need to combine multiple components, consider the Composite Pattern instead.

Q: How do you handle decorator removal at runtime?

A: This is tricky. You’d need to keep references to each layer and reconstruct the chain without the unwanted decorator. This is a sign that decorators might not be the best choice — consider the Strategy Pattern for swappable behavior.

Coding Exercise to Practice

Implement a notification system where you can send notifications via email, SMS, and Slack. Use decorators to add features like:

  • Logging (records when notifications are sent)
  • Retry logic (retries failed sends)
  • Rate limiting (prevents sending too many notifications)

Make sure your solution allows combining these features in any order.

Red Flags to Avoid

  • Don’t confuse Python’s @decorator syntax with the Decorator Pattern — they’re related but not the same
  • Don’t say “decorators are always better than inheritance” — know when each is appropriate
  • Don’t forget to implement the same interface in your decorators
  • Don’t create decorators that depend on specific decorator order without documenting it

Key Takeaways

  • The Decorator Pattern adds responsibilities to objects dynamically by wrapping them in decorator classes that implement the same interface, providing a flexible alternative to subclassing.

  • Each decorator wraps a component and delegates to it while adding its own behavior before or after the delegation, allowing you to stack multiple decorators to combine behaviors.

  • Use decorators when you need runtime flexibility and have many possible combinations of features; use inheritance when you have a fixed, small set of types known at compile time.

  • Decorators follow the Open/Closed Principle — you can extend functionality without modifying existing code by adding new decorator classes.

  • Order matters in decorator chains — different stacking orders can produce different results, so document expected order or design decorators to be order-independent when possible.