Skip to content

Decorator Pattern

Category: Structural Pattern

Overview

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality, allowing behavior to be added to individual objects without affecting other objects of the same class.

Usage Guidelines

Use when:

  • Need to add responsibilities to objects at runtime
  • Features should be added and removed dynamically
  • Want to combine features in various ways
  • Existing classes should remain unchanged (Open/Closed Principle)

Avoid when:

  • All behavior is known at compile time and doesn't change
  • A single subclass would suffice for simple enhancements
  • The order of applying decorators significantly affects behavior and causes confusion
  • Multiple wrapper layers introduce unacceptable performance overhead

Implementation

from abc import ABC, abstractmethod

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

    @abstractmethod
    def get_description(self) -> str:
        pass

# Concrete Component
class SimpleCoffee(Coffee):
    def get_cost(self) -> float:
        return 1.0

    def get_description(self) -> str:
        return "Simple Coffee"

# Base Decorator
class CoffeeDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee

    def get_cost(self) -> float:
        return self._coffee.get_cost()

    def get_description(self) -> str:
        return self._coffee.get_description()

# Concrete Decorators
class MilkDecorator(CoffeeDecorator):
    def get_cost(self) -> float:
        return self._coffee.get_cost() + 0.5

    def get_description(self) -> str:
        return self._coffee.get_description() + ", Milk"

class SugarDecorator(CoffeeDecorator):
    def get_cost(self) -> float:
        return self._coffee.get_cost() + 0.2

    def get_description(self) -> str:
        return self._coffee.get_description() + ", Sugar"

class VanillaDecorator(CoffeeDecorator):
    def get_cost(self) -> float:
        return self._coffee.get_cost() + 0.7

    def get_description(self) -> str:
        return self._coffee.get_description() + ", Vanilla"

Usage

# Start with simple coffee
coffee = SimpleCoffee()
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee: $1.00

# Add milk
coffee = MilkDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk: $1.50

# Add sugar
coffee = SugarDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk, Sugar: $1.70

# Add vanilla
coffee = VanillaDecorator(coffee)
print(f"{coffee.get_description()}: ${coffee.get_cost():.2f}")
# Output: Simple Coffee, Milk, Sugar, Vanilla: $2.40

Trade-offs

Benefits:

  1. Add or remove responsibilities at runtime for flexibility
  2. Extend functionality without modifying existing code (Open/Closed Principle)
  3. Each decorator focuses on one concern (Single Responsibility)
  4. Mix and match decorators for different combinations

Drawbacks:

  1. Many small objects and layers can be hard to understand
  2. Decorator order may matter, causing confusion
  3. Decorated objects differ from original objects causing identity issues
  4. Stack traces show multiple wrapper layers making debugging difficult

Real-World Examples

  • I/O streams with BufferedInputStream, GZIPInputStream wrapping basic streams
  • GUI components adding scrollbars, borders to UI elements
  • Middleware adding logging, authentication, caching to HTTP handlers
  • Caching layers adding caching to database or API calls
  • Adapter
  • Composite
  • Proxy
  • Strategy
  • Chain of Responsibility

API Reference

design_patterns.structural.decorator

Decorator Pattern Module

The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. This pattern allows behavior to be added to individual objects without affecting other objects of the same class.

Example

Decorating a coffee order:

coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)

print(coffee.get_description())  # "Simple Coffee, Milk, Sugar"
print(coffee.get_cost())  # 3.0

Coffee

Bases: ABC

Abstract base class for coffee.

Source code in src/design_patterns/structural/decorator.py
class Coffee(ABC):
    """Abstract base class for coffee."""

    @abstractmethod
    def get_cost(self) -> float:
        """Get the cost of the coffee.

        Returns:
            The cost in dollars.
        """
        pass

    @abstractmethod
    def get_description(self) -> str:
        """Get the description of the coffee.

        Returns:
            Coffee description.
        """
        pass

get_cost() abstractmethod

Get the cost of the coffee.

Returns:

Type Description
float

The cost in dollars.

Source code in src/design_patterns/structural/decorator.py
@abstractmethod
def get_cost(self) -> float:
    """Get the cost of the coffee.

    Returns:
        The cost in dollars.
    """
    pass

get_description() abstractmethod

Get the description of the coffee.

Returns:

Type Description
str

Coffee description.

Source code in src/design_patterns/structural/decorator.py
@abstractmethod
def get_description(self) -> str:
    """Get the description of the coffee.

    Returns:
        Coffee description.
    """
    pass

CoffeeDecorator

Bases: Coffee

Base decorator class for coffee add-ons.

Source code in src/design_patterns/structural/decorator.py
class CoffeeDecorator(Coffee):
    """Base decorator class for coffee add-ons."""

    def __init__(self, coffee: Coffee) -> None:
        """Initialize decorator with a coffee instance.

        Args:
            coffee: The coffee to decorate.
        """
        self._coffee = coffee

    def get_cost(self) -> float:
        """Get cost including this decorator.

        Returns:
            Total cost.
        """
        return self._coffee.get_cost()

    def get_description(self) -> str:
        """Get description including this decorator.

        Returns:
            Complete description.
        """
        return self._coffee.get_description()

__init__(coffee)

Initialize decorator with a coffee instance.

Parameters:

Name Type Description Default
coffee Coffee

The coffee to decorate.

required
Source code in src/design_patterns/structural/decorator.py
def __init__(self, coffee: Coffee) -> None:
    """Initialize decorator with a coffee instance.

    Args:
        coffee: The coffee to decorate.
    """
    self._coffee = coffee

get_cost()

Get cost including this decorator.

Returns:

Type Description
float

Total cost.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get cost including this decorator.

    Returns:
        Total cost.
    """
    return self._coffee.get_cost()

get_description()

Get description including this decorator.

Returns:

Type Description
str

Complete description.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get description including this decorator.

    Returns:
        Complete description.
    """
    return self._coffee.get_description()

CompressionDecorator

Bases: DataSourceDecorator

Decorator that adds compression to data operations.

Source code in src/design_patterns/structural/decorator.py
class CompressionDecorator(DataSourceDecorator):
    """Decorator that adds compression to data operations."""

    def write_data(self, data: str) -> None:
        """Write compressed data.

        Args:
            data: Data to compress and write.
        """
        compressed = self._compress(data)
        self._source.write_data(compressed)

    def read_data(self) -> str:
        """Read and decompress data.

        Returns:
            Decompressed data.
        """
        compressed = self._source.read_data()
        return self._decompress(compressed)

    def _compress(self, data: str) -> str:
        """Simple compression simulation (prefix with marker).

        Args:
            data: Data to compress.

        Returns:
            Compressed data.
        """
        return f"[COMPRESSED]{data}"

    def _decompress(self, data: str) -> str:
        """Simple decompression simulation (remove prefix).

        Args:
            data: Data to decompress.

        Returns:
            Decompressed data.
        """
        if data.startswith("[COMPRESSED]"):
            return data[12:]
        return data

read_data()

Read and decompress data.

Returns:

Type Description
str

Decompressed data.

Source code in src/design_patterns/structural/decorator.py
def read_data(self) -> str:
    """Read and decompress data.

    Returns:
        Decompressed data.
    """
    compressed = self._source.read_data()
    return self._decompress(compressed)

write_data(data)

Write compressed data.

Parameters:

Name Type Description Default
data str

Data to compress and write.

required
Source code in src/design_patterns/structural/decorator.py
def write_data(self, data: str) -> None:
    """Write compressed data.

    Args:
        data: Data to compress and write.
    """
    compressed = self._compress(data)
    self._source.write_data(compressed)

DataSource

Bases: ABC

Abstract interface for data sources.

Source code in src/design_patterns/structural/decorator.py
class DataSource(ABC):
    """Abstract interface for data sources."""

    @abstractmethod
    def write_data(self, data: str) -> None:
        """Write data to the source.

        Args:
            data: The data to write.
        """
        pass

    @abstractmethod
    def read_data(self) -> str:
        """Read data from the source.

        Returns:
            The data.
        """
        pass

read_data() abstractmethod

Read data from the source.

Returns:

Type Description
str

The data.

Source code in src/design_patterns/structural/decorator.py
@abstractmethod
def read_data(self) -> str:
    """Read data from the source.

    Returns:
        The data.
    """
    pass

write_data(data) abstractmethod

Write data to the source.

Parameters:

Name Type Description Default
data str

The data to write.

required
Source code in src/design_patterns/structural/decorator.py
@abstractmethod
def write_data(self, data: str) -> None:
    """Write data to the source.

    Args:
        data: The data to write.
    """
    pass

DataSourceDecorator

Bases: DataSource

Base decorator for data sources.

Source code in src/design_patterns/structural/decorator.py
class DataSourceDecorator(DataSource):
    """Base decorator for data sources."""

    def __init__(self, source: DataSource) -> None:
        """Initialize decorator.

        Args:
            source: The data source to decorate.
        """
        self._source = source

    def write_data(self, data: str) -> None:
        """Write data through decorator.

        Args:
            data: Data to write.
        """
        self._source.write_data(data)

    def read_data(self) -> str:
        """Read data through decorator.

        Returns:
            The data.
        """
        return self._source.read_data()

__init__(source)

Initialize decorator.

Parameters:

Name Type Description Default
source DataSource

The data source to decorate.

required
Source code in src/design_patterns/structural/decorator.py
def __init__(self, source: DataSource) -> None:
    """Initialize decorator.

    Args:
        source: The data source to decorate.
    """
    self._source = source

read_data()

Read data through decorator.

Returns:

Type Description
str

The data.

Source code in src/design_patterns/structural/decorator.py
def read_data(self) -> str:
    """Read data through decorator.

    Returns:
        The data.
    """
    return self._source.read_data()

write_data(data)

Write data through decorator.

Parameters:

Name Type Description Default
data str

Data to write.

required
Source code in src/design_patterns/structural/decorator.py
def write_data(self, data: str) -> None:
    """Write data through decorator.

    Args:
        data: Data to write.
    """
    self._source.write_data(data)

EncryptionDecorator

Bases: DataSourceDecorator

Decorator that adds encryption to data operations.

Source code in src/design_patterns/structural/decorator.py
class EncryptionDecorator(DataSourceDecorator):
    """Decorator that adds encryption to data operations."""

    def write_data(self, data: str) -> None:
        """Write encrypted data.

        Args:
            data: Data to encrypt and write.
        """
        encrypted = self._encrypt(data)
        self._source.write_data(encrypted)

    def read_data(self) -> str:
        """Read and decrypt data.

        Returns:
            Decrypted data.
        """
        encrypted = self._source.read_data()
        return self._decrypt(encrypted)

    def _encrypt(self, data: str) -> str:
        """Simple encryption (reversed string).

        Args:
            data: Data to encrypt.

        Returns:
            Encrypted data.
        """
        return data[::-1]

    def _decrypt(self, data: str) -> str:
        """Simple decryption (reverse the reversed string).

        Args:
            data: Data to decrypt.

        Returns:
            Decrypted data.
        """
        return data[::-1]

read_data()

Read and decrypt data.

Returns:

Type Description
str

Decrypted data.

Source code in src/design_patterns/structural/decorator.py
def read_data(self) -> str:
    """Read and decrypt data.

    Returns:
        Decrypted data.
    """
    encrypted = self._source.read_data()
    return self._decrypt(encrypted)

write_data(data)

Write encrypted data.

Parameters:

Name Type Description Default
data str

Data to encrypt and write.

required
Source code in src/design_patterns/structural/decorator.py
def write_data(self, data: str) -> None:
    """Write encrypted data.

    Args:
        data: Data to encrypt and write.
    """
    encrypted = self._encrypt(data)
    self._source.write_data(encrypted)

FileDataSource

Bases: DataSource

Basic file data source.

Source code in src/design_patterns/structural/decorator.py
class FileDataSource(DataSource):
    """Basic file data source."""

    def __init__(self, filename: str) -> None:
        """Initialize file data source.

        Args:
            filename: The file name.
        """
        self.filename = filename
        self._data: str = ""

    def write_data(self, data: str) -> None:
        """Write data to file.

        Args:
            data: Data to write.
        """
        self._data = data

    def read_data(self) -> str:
        """Read data from file.

        Returns:
            The stored data.
        """
        return self._data

__init__(filename)

Initialize file data source.

Parameters:

Name Type Description Default
filename str

The file name.

required
Source code in src/design_patterns/structural/decorator.py
def __init__(self, filename: str) -> None:
    """Initialize file data source.

    Args:
        filename: The file name.
    """
    self.filename = filename
    self._data: str = ""

read_data()

Read data from file.

Returns:

Type Description
str

The stored data.

Source code in src/design_patterns/structural/decorator.py
def read_data(self) -> str:
    """Read data from file.

    Returns:
        The stored data.
    """
    return self._data

write_data(data)

Write data to file.

Parameters:

Name Type Description Default
data str

Data to write.

required
Source code in src/design_patterns/structural/decorator.py
def write_data(self, data: str) -> None:
    """Write data to file.

    Args:
        data: Data to write.
    """
    self._data = data

MilkDecorator

Bases: CoffeeDecorator

Decorator that adds milk to coffee.

Source code in src/design_patterns/structural/decorator.py
class MilkDecorator(CoffeeDecorator):
    """Decorator that adds milk to coffee."""

    def get_cost(self) -> float:
        """Get cost with milk added.

        Returns:
            Cost plus 0.5 for milk.
        """
        return self._coffee.get_cost() + 0.5

    def get_description(self) -> str:
        """Get description with milk.

        Returns:
            Description with milk added.
        """
        return self._coffee.get_description() + ", Milk"

get_cost()

Get cost with milk added.

Returns:

Type Description
float

Cost plus 0.5 for milk.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get cost with milk added.

    Returns:
        Cost plus 0.5 for milk.
    """
    return self._coffee.get_cost() + 0.5

get_description()

Get description with milk.

Returns:

Type Description
str

Description with milk added.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get description with milk.

    Returns:
        Description with milk added.
    """
    return self._coffee.get_description() + ", Milk"

SimpleCoffee

Bases: Coffee

Basic coffee without any additions.

Source code in src/design_patterns/structural/decorator.py
class SimpleCoffee(Coffee):
    """Basic coffee without any additions."""

    def get_cost(self) -> float:
        """Get base coffee cost.

        Returns:
            Base cost of 1.0.
        """
        return 1.0

    def get_description(self) -> str:
        """Get base coffee description.

        Returns:
            Description string.
        """
        return "Simple Coffee"

get_cost()

Get base coffee cost.

Returns:

Type Description
float

Base cost of 1.0.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get base coffee cost.

    Returns:
        Base cost of 1.0.
    """
    return 1.0

get_description()

Get base coffee description.

Returns:

Type Description
str

Description string.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get base coffee description.

    Returns:
        Description string.
    """
    return "Simple Coffee"

SugarDecorator

Bases: CoffeeDecorator

Decorator that adds sugar to coffee.

Source code in src/design_patterns/structural/decorator.py
class SugarDecorator(CoffeeDecorator):
    """Decorator that adds sugar to coffee."""

    def get_cost(self) -> float:
        """Get cost with sugar added.

        Returns:
            Cost plus 0.2 for sugar.
        """
        return self._coffee.get_cost() + 0.2

    def get_description(self) -> str:
        """Get description with sugar.

        Returns:
            Description with sugar added.
        """
        return self._coffee.get_description() + ", Sugar"

get_cost()

Get cost with sugar added.

Returns:

Type Description
float

Cost plus 0.2 for sugar.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get cost with sugar added.

    Returns:
        Cost plus 0.2 for sugar.
    """
    return self._coffee.get_cost() + 0.2

get_description()

Get description with sugar.

Returns:

Type Description
str

Description with sugar added.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get description with sugar.

    Returns:
        Description with sugar added.
    """
    return self._coffee.get_description() + ", Sugar"

VanillaDecorator

Bases: CoffeeDecorator

Decorator that adds vanilla to coffee.

Source code in src/design_patterns/structural/decorator.py
class VanillaDecorator(CoffeeDecorator):
    """Decorator that adds vanilla to coffee."""

    def get_cost(self) -> float:
        """Get cost with vanilla added.

        Returns:
            Cost plus 0.7 for vanilla.
        """
        return self._coffee.get_cost() + 0.7

    def get_description(self) -> str:
        """Get description with vanilla.

        Returns:
            Description with vanilla added.
        """
        return self._coffee.get_description() + ", Vanilla"

get_cost()

Get cost with vanilla added.

Returns:

Type Description
float

Cost plus 0.7 for vanilla.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get cost with vanilla added.

    Returns:
        Cost plus 0.7 for vanilla.
    """
    return self._coffee.get_cost() + 0.7

get_description()

Get description with vanilla.

Returns:

Type Description
str

Description with vanilla added.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get description with vanilla.

    Returns:
        Description with vanilla added.
    """
    return self._coffee.get_description() + ", Vanilla"

WhippedCreamDecorator

Bases: CoffeeDecorator

Decorator that adds whipped cream to coffee.

Source code in src/design_patterns/structural/decorator.py
class WhippedCreamDecorator(CoffeeDecorator):
    """Decorator that adds whipped cream to coffee."""

    def get_cost(self) -> float:
        """Get cost with whipped cream added.

        Returns:
            Cost plus 0.8 for whipped cream.
        """
        return self._coffee.get_cost() + 0.8

    def get_description(self) -> str:
        """Get description with whipped cream.

        Returns:
            Description with whipped cream added.
        """
        return self._coffee.get_description() + ", Whipped Cream"

get_cost()

Get cost with whipped cream added.

Returns:

Type Description
float

Cost plus 0.8 for whipped cream.

Source code in src/design_patterns/structural/decorator.py
def get_cost(self) -> float:
    """Get cost with whipped cream added.

    Returns:
        Cost plus 0.8 for whipped cream.
    """
    return self._coffee.get_cost() + 0.8

get_description()

Get description with whipped cream.

Returns:

Type Description
str

Description with whipped cream added.

Source code in src/design_patterns/structural/decorator.py
def get_description(self) -> str:
    """Get description with whipped cream.

    Returns:
        Description with whipped cream added.
    """
    return self._coffee.get_description() + ", Whipped Cream"