Design Patterns Decorator

The Decorator pattern adds new behavior to an object at runtime without changing the object's class. Think of it like a coffee order — you start with a plain espresso and then add milk, sugar, and caramel on top. Each addition wraps around the previous cup and enhances it. The espresso itself never changes.

Decorator belongs to the Structural pattern family. It gives you a flexible alternative to subclassing when you need to extend object behavior.

The Coffee Shop Analogy

A basic espresso costs $2.00. A customer adds milk (+$0.50), then caramel (+$0.75), then whipped cream (+$0.60). Each "add-on" wraps around the previous order and contributes its price and description. The base espresso object stays simple and unchanged.

Base Object:     Espresso → cost: $2.00, desc: "Espresso"
  Wrap with:     MilkDecorator → cost: +$0.50
    Wrap with:   CaramelDecorator → cost: +$0.75
      Wrap with: WhipDecorator → cost: +$0.60

Final cost:  $2.00 + $0.50 + $0.75 + $0.60 = $3.85
Final desc:  "Espresso, Milk, Caramel, Whip"

Visual Diagram: Decorator Layering

+----------------------------+
|  WhipDecorator             |  ← outermost wrapper
|  +------------------------+|
|  |  CaramelDecorator      ||  ← second wrapper
|  |  +--------------------+||
|  |  |  MilkDecorator     |||  ← first wrapper
|  |  |  +--------------+  |||
|  |  |  |  Espresso    |  |||  ← original object
|  |  |  |  cost: $2.00 |  |||
|  |  |  +--------------+  |||
|  |  +--------------------+||
|  +------------------------+|
+----------------------------+

Each layer calls the inner layer's cost() and adds its own.

Key Participants

Component Interface

Defines the operations that both the original object and all decorators must support. This is the contract that keeps everything interchangeable.

Concrete Component

The base object — the thing you want to decorate. It provides the default behavior that decorators build on.

Base Decorator

Holds a reference to a Component and delegates all operations to it. All concrete decorators extend this class. The reference is the key — it allows decorators to chain.

Concrete Decorator

Extends the Base Decorator, adds its own behavior before or after delegating to the wrapped object.

Structure Diagram

«interface»
Component
+------------------+
| + operation()    |
+------------------+
      ▲         ▲
      |         |
ConcreteComponent  BaseDecorator
+------------+    +------------------+
| operation()|    | component: Comp  |
+------------+    | + operation()    |
                  +------------------+
                        ▲
                        |
              ConcreteDecoratorA    ConcreteDecoratorB
              +---------------+    +---------------+
              | extra state   |    | + operation() |
              | + operation() |    +---------------+
              +---------------+

Code Walkthrough in Python

Step 1 — Define the Component Interface

class Coffee:
    def cost(self):
        raise NotImplementedError

    def description(self):
        raise NotImplementedError

Step 2 — Create the Concrete Component

class Espresso(Coffee):
    def cost(self):
        return 2.00

    def description(self):
        return "Espresso"

Step 3 — Build the Base Decorator

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

    def cost(self):
        return self._coffee.cost()

    def description(self):
        return self._coffee.description()

Step 4 — Create Concrete Decorators

class Milk(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.50

    def description(self):
        return self._coffee.description() + ", Milk"

class Caramel(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.75

    def description(self):
        return self._coffee.description() + ", Caramel"

class WhippedCream(CoffeeDecorator):
    def cost(self):
        return self._coffee.cost() + 0.60

    def description(self):
        return self._coffee.description() + ", Whip"

Step 5 — Assemble the Order

drink = Espresso()
drink = Milk(drink)
drink = Caramel(drink)
drink = WhippedCream(drink)

print(drink.description())  # Espresso, Milk, Caramel, Whip
print(f"${drink.cost():.2f}")  # $3.85

How the Call Chain Flows

WhippedCream.cost()
  calls → Caramel.cost()
    calls → Milk.cost()
      calls → Espresso.cost() → returns 2.00
    Milk returns 2.00 + 0.50 = 2.50
  Caramel returns 2.50 + 0.75 = 3.25
WhippedCream returns 3.25 + 0.60 = 3.85

Each decorator adds its value on top of whatever the inner object returns. The chain resolves from the inside out.

Decorator vs Subclassing

Suppose you need 4 coffee types: Espresso, Latte, Cappuccino, Mocha — and 3 add-ons: Milk, Caramel, Whip. Without Decorator, subclassing every combination produces:

SUBCLASSING APPROACH (class explosion):
  EspressoWithMilk
  EspressoWithMilkAndCaramel
  EspressoWithMilkAndCaramelAndWhip
  LatteWithMilk
  LatteWithCaramel
  ... (4 bases × 2³ combinations = 32 classes)

DECORATOR APPROACH:
  4 base classes + 3 decorator classes = 7 classes total
  All 32 combinations built at runtime by stacking decorators.

Real-World Uses of Decorator

USE CASE                        WHAT GETS DECORATED
-----------------------------------------------------
Python's @functools.lru_cache   Adds caching to any function
Java's InputStream wrappers     Adds buffering, compression,
  BufferedInputStream             encryption to raw streams
  GZipInputStream
UI component borders/padding    Adds visual wrappers around
  ScrollDecorator                 base widgets in GUI toolkits
  BorderDecorator
Logging middleware              Wraps API calls to log
                                  request/response data
Authentication middleware       Wraps request handlers to
                                  check tokens before executing

Decorator in Python: The @decorator Syntax

Python uses the Decorator pattern natively with the @ symbol on functions.

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Done {func.__name__}")
        return result
    return wrapper

@log_calls
def process_payment(amount):
    print(f"Processing ${amount}")

process_payment(50)
# Output:
# Calling process_payment
# Processing $50
# Done process_payment

log_calls wraps process_payment without touching the original function — pure Decorator pattern.

When to Use the Decorator Pattern

Use Decorator When:

  • You want to add responsibilities to objects without altering their class.
  • Extension by subclassing would produce too many classes.
  • You need to add or remove behaviors dynamically at runtime.
  • Several independent extensions should be combinable in any order.

Avoid Decorator When:

  • The component interface is large — every decorator must implement all methods, even ones it does not change.
  • Order of decorators matters and can cause bugs — document wrapper order clearly.

Advantages and Disadvantages

Advantages

  • Extends behavior without modifying existing classes — follows the Open/Closed Principle.
  • Mixes and matches responsibilities at runtime.
  • Avoids class explosion caused by every subclass combination.

Disadvantages

  • Many small wrapper objects can make debugging harder — the call stack grows deep.
  • A decorator is not the same type as the concrete component at runtime — identity checks like isinstance(obj, Espresso) fail on decorated objects.

Quick Summary

DECORATOR PATTERN — AT A GLANCE

Problem:   Adding features via subclassing creates too many
           classes; you need to add behavior dynamically.
Solution:  Wrap objects in decorator classes that add behavior
           before/after delegating to the wrapped object.
Key idea:  Decorators share the same interface as the object
           they wrap — clients treat them identically.
Best for:  I/O streams, middleware stacks, UI components,
           logging, caching, authentication layers.

The Decorator pattern gives objects new powers at runtime without rewriting a single line of the original class. Layer decorators freely, combine them in any order, and remove them just as easily.

Leave a Comment