2.5 Decorator
The Decorator pattern lets you add responsibilities to an object dynamically at runtime, without modifying its class or affecting other objects of the same class. It is one of the most elegant expressions of "favor composition over inheritance."
Picture a coffee: the base is espresso, and you can add milk, mocha, whip. Expressing every combination with inheritance gives you absurd classes like MilkMochaWhipEspresso, and N add-ons produce 2^N subclasses. Decorator flips it: each add-on is a "wrapper" that goes around the drink, stacking price and description.
In the add-on bar below, each ingredient you toggle wraps one more decorator, showing the nested new Whip(new Milk(new Espresso())) and the final price live.
Decorator and component share an interface
interface Beverage { double cost(); }
class Espresso implements Beverage {
public double cost() { return 18; }
}
// Abstract decorator: implements the same interface and wraps a Beverage
abstract class AddOn implements Beverage {
protected Beverage inner;
AddOn(Beverage inner) { this.inner = inner; }
}
class Milk extends AddOn {
Milk(Beverage inner) { super(inner); }
public double cost() { return inner.cost() + 3; } // stack
}Because a decorator is itself a Beverage, it can be decorated again, nesting layer by layer.
Order sometimes matters
When decorators have side effects or transform data, the stacking order changes the result. Java IO streams are the classic case: new BufferedReader(new InputStreamReader(...)). Compress-then-encrypt and encrypt-then-compress produce entirely different output. The stacker below lets you reorder the three layers "compress / encrypt / base64" and watch the write() wrapping direction change.
Decorator vs inheritance vs proxy
- vs inheritance: inheritance is fixed at compile time; a decorator composes at runtime. Choose a decorator when you need "arbitrary combination, dynamic stacking."
- vs proxy: the two have nearly identical structure (both wrap an object, both share its interface); the difference is intent. A decorator enhances functionality; a proxy controls access (lazy loading, auth, remoting).