Structural

Decorator

Attach additional responsibilities to an object dynamically — a flexible alternative to subclassing for extending functionality.

Intermediate Pattern #09 of 23

What is the Decorator Pattern?

Decorator wraps an object inside another object that adds new behaviour before or after delegating to the wrapped object. Both the wrapper and the wrapped object implement the same interface — so the client can't tell the difference.

Decorators can be stacked: wrap a decorator inside another decorator to layer multiple behaviours. This is far more flexible than subclassing, which bakes behaviour in at compile time.

Real-world analogy: Ordering coffee. A plain espresso is the base. Add milk → MilkDecorator wraps it. Add sugar → SugarDecorator wraps that. Each wrapper adds its cost and description while still being "a coffee."

The Problem It Solves

You have a TextEditor class. You want variants: one with spell-check, one with auto-save, one with both, one with syntax highlighting too. Using inheritance you'd need SpellCheckEditor, AutoSaveEditor, SpellCheckAutoSaveEditor... a combinatorial explosion.

Decorator lets you wrap the base editor in a SpellCheckDecorator, then optionally wrap that in an AutoSaveDecorator at runtime — combining freely without any new subclasses.

Participants

Component (interface)
Defines the interface for objects that can have responsibilities added dynamically. Both the concrete component and all decorators implement this.
ConcreteComponent
The base object being decorated. Defines the core behaviour that decorators will extend.
Decorator (abstract)
Wraps a Component reference. Implements Component and delegates all calls to the wrapped object. The critical middle layer.
ConcreteDecorator
Adds specific behaviour before/after calling the wrapped component's method.

Visual Flow Diagram

«interface» Component operation() ConcreteComponent operation() — core work Decorator - wrapped: Component operation() → wrapped.operation() MilkDecorator SugarDecorator wraps Stacking order: SugarDec ↓ wraps MilkDec ↓ wraps Espresso (base)

Java Code Example

Java — Coffee Shop Example
// Component interface
public interface Coffee {
    String getDescription();
    double getCost();
}

// ConcreteComponent — base object
public class Espresso implements Coffee {
    public String getDescription() { return "Espresso"; }
    public double getCost()        { return 1.00; }
}
public class SimpleCoffee implements Coffee {
    public String getDescription() { return "Simple Coffee"; }
    public double getCost()        { return 0.75; }
}

// Abstract Decorator — wraps a Coffee, delegates all calls
public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee wrapped; // THE WRAPPED COMPONENT

    public CoffeeDecorator(Coffee coffee) {
        this.wrapped = coffee;
    }

    // Default: delegate to wrapped (subclasses override to add behaviour)
    public String getDescription() { return wrapped.getDescription(); }
    public double getCost()        { return wrapped.getCost(); }
}

// Concrete Decorators — add specific behaviours
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }

    public String getDescription() {
        return wrapped.getDescription() + ", Milk"; // add after
    }
    public double getCost() { return wrapped.getCost() + 0.25; }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }

    public String getDescription() {
        return wrapped.getDescription() + ", Sugar";
    }
    public double getCost() { return wrapped.getCost() + 0.10; }
}

public class VanillaDecorator extends CoffeeDecorator {
    public VanillaDecorator(Coffee coffee) { super(coffee); }

    public String getDescription() {
        return wrapped.getDescription() + ", Vanilla";
    }
    public double getCost() { return wrapped.getCost() + 0.50; }
}

// Client — stacks decorators freely at runtime
public class CoffeeShop {
    public static void main(String[] args) {
        // Plain espresso
        Coffee order = new Espresso();
        System.out.println(order.getDescription() + " $" + order.getCost());
        // Espresso $1.00

        // Add milk
        order = new MilkDecorator(order);
        // Add sugar
        order = new SugarDecorator(order);
        // Add vanilla
        order = new VanillaDecorator(order);

        System.out.println(order.getDescription() + " $" + order.getCost());
        // Espresso, Milk, Sugar, Vanilla $1.85

        // Same base, different combination — no new subclass needed
        Coffee plain = new SugarDecorator(new SimpleCoffee());
        System.out.println(plain.getDescription() + " $" + plain.getCost());
        // Simple Coffee, Sugar $0.85
    }
}

When to Use / Avoid

✓ Use When

  • You need to add behaviour to objects without modifying their class
  • You want to combine behaviours at runtime in any combination
  • Subclassing would cause a class explosion for all combinations
  • You want to add/remove responsibilities transparently at runtime

✕ Avoid When

  • The order of wrapping matters in complex, unpredictable ways — hard to debug
  • You need to remove a specific middle decorator from a chain
  • The component interface is large — decorators must delegate every method

Real-World Examples

Pros & Cons

Pros

  • Extend behaviour without subclassing — Open/Closed Principle
  • Combine behaviours at runtime in any order and quantity
  • Single Responsibility — each decorator does exactly one thing
  • Transparent to client — same interface throughout the chain

Cons

  • Hard to remove a specific decorator from the middle of a chain
  • Order of wrapping can affect behaviour in subtle ways
  • Debugging is tricky — stack traces traverse many wrapper layers
  • Many small objects — can be confusing when there are too many decorators

How Decorator Can Be Broken

⚠ Attack Vectors

  • Forgetting to delegate: A ConcreteDecorator overrides a method but forgets to call wrapped.method() — silently drops the base behaviour and all inner decorators
  • Decorating the wrong type: Nothing stops wrapping a Decorator around an incompatible Component at runtime if the interface is too broad — the chain produces garbage output
  • State mutation in decorators: A decorator caches/mutates state from the wrapped component — when the inner component changes, the decorator's cache is stale
  • Identity checks fail: obj instanceof Espresso returns false when wrapped — code relying on type identity breaks silently
  • Infinite wrapping: A decorator wraps itself accidentally in a loop — every call recurses infinitely

✓ Prevention

  • Always call super in abstract decorator: The base CoffeeDecorator default implementation delegates to wrapped — subclasses call super.method() to stay in the chain
  • Null-check the wrapped component: Validate in the Decorator constructor — a null wrapped object will cause NPE deep in the call chain with no clear error
  • Never cache wrapped state: Always call wrapped.method() live — never read a field from the wrapped object and store it
  • Avoid identity checks: Design the system so behaviour is determined by the Component interface, never by instanceof checks on the decorated type
  • Guard against self-wrapping: In the constructor, throw if coffee == this to prevent infinite recursion
Java — Break & Fix
// ❌ BREAKING — forgetting to delegate
public class BrokenMilkDecorator extends CoffeeDecorator {
    public BrokenMilkDecorator(Coffee c) { super(c); }
    public double getCost() {
        return 0.25; // ❌ forgot wrapped.getCost() — base cost lost!
    }
}

// ✅ FIX — always delegate to wrapped
public double getCost() {
    return wrapped.getCost() + 0.25; // ✅ chain preserved
}

// ❌ BREAKING — null wrapped object
Coffee bad = new MilkDecorator(null); // NPE on getCost()

// ✅ FIX — validate in constructor
public CoffeeDecorator(Coffee coffee) {
    if (coffee == null)
        throw new IllegalArgumentException("Wrapped coffee cannot be null");
    this.wrapped = coffee;
}

// ❌ BREAKING — identity check fails after decorating
Coffee order = new MilkDecorator(new Espresso());
if (order instanceof Espresso) { // false! now MilkDecorator
    // logic never runs — silent bug
}

// ✅ FIX — use interface methods, never instanceof on wrapped type
// Add a method to the Coffee interface if you need to query type
public interface Coffee {
    String getDescription();
    double getCost();
    boolean isEspressoBase(); // let each type decide
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Wraps an object in layers to add behaviour at runtime without subclassing. Both wrapper and wrapped implement the same interface — client can't tell the difference. Decorators stack freely.
  2. How: Component interface. ConcreteComponent = base. Abstract Decorator holds a wrapped Component field. ConcreteDecorators call wrapped.method() and add before/after. Stack by passing one into another's constructor.
  3. Key distinction: Decorator = same interface + adds behaviour. Proxy = same interface + controls access. Adapter = changes interface. The intent is what separates them — not the structure.