The Decorator pattern attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing.

Problem

You need to add behavior to individual objects without affecting others, and subclassing leads to an explosion of classes.

Solution

  interface Coffee {
    String getDescription();
    double getCost();
}

class SimpleCoffee implements Coffee {
    public String getDescription() { return "Simple coffee"; }
    public double getCost() { return 2.0; }
}

abstract class CoffeeDecorator implements Coffee {
    protected final Coffee wrapped;
    CoffeeDecorator(Coffee coffee) { this.wrapped = coffee; }
}

class MilkDecorator extends CoffeeDecorator {
    MilkDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() { return wrapped.getDescription() + ", milk"; }
    public double getCost() { return wrapped.getCost() + 0.5; }
}

class SugarDecorator extends CoffeeDecorator {
    SugarDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() { return wrapped.getDescription() + ", sugar"; }
    public double getCost() { return wrapped.getCost() + 0.2; }
}

// Usage — stack decorators
Coffee coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee.getDescription()); // Simple coffee, milk, sugar
System.out.println(coffee.getCost());         // 2.7
  

Java I/O Decorators

The classic Java example — I/O streams are decorators:

  InputStream input = new BufferedInputStream(
    new GZIPInputStream(
        new FileInputStream("data.gz")
    )
);
// BufferedInputStream decorates GZIPInputStream decorates FileInputStream
  

Each wrapper adds behavior: file access → decompression → buffering.

Decorator vs Inheritance

Decorator Inheritance
Runtime composition Compile-time extension
Stack multiple behaviors One level of extension
Same interface Subclass interface
Flexible Rigid

Java Examples

  Collections.synchronizedList(list);    // thread-safe decorator
Collections.unmodifiableList(list);    // read-only decorator
new BufferedReader(new FileReader(f)); // buffering decorator
  

When to Use

  • Add responsibilities to objects individually and dynamically
  • Subclassing would produce too many combinations
  • You want to wrap objects with cross-cutting concerns (logging, caching)

Best Practices

  • Keep the decorator interface identical to the wrapped object
  • Order of decorators may matter — document dependencies
  • Consider whether AOP (Spring @Aspect) is cleaner for cross-cutting concerns
  • Do not over-decorate — deep chains are hard to debug