Structural Wrapper

Decorator

Decorator is a structural pattern that adds behavior to an individual object by wrapping it in layers that share its interface, without touching the object's class or affecting other instances.

Complexity
Popularity

Problem

A coffee shop's ordering system starts with a `Coffee` class that has a `cost()` method. Customers can add milk, an extra shot, whipped cream, or any combination of the three. Modeling every combination as a subclass - `CoffeeWithMilk`, `CoffeeWithMilkAndShot`, `CoffeeWithMilkAndShotAndCream` - multiplies out of control as more add-ons are introduced, and most of those subclasses only differ by which extras they tack onto the price.

Worse, inheritance locks the combination in at class-definition time. If a customer wants to add whipped cream to an order that's already in progress, there's no subclass for "whatever this order already is, plus cream."

Solution

Give each add-on its own small wrapper class that implements the same interface as the base object and holds a reference to whatever it's wrapping. Calling `cost()` on a wrapper first asks the wrapped object for its cost, then adds its own charge on top.

Because every wrapper looks exactly like the object it wraps, wrappers stack: a `Coffee` can be wrapped in a `MilkDecorator`, then that result wrapped again in a `WhipDecorator`, and the outermost object still answers to `cost()` and any other shared method. Combinations are assembled at runtime by choosing which wrappers to apply, instead of being baked into a fixed subclass.

When to Use

  • You need to attach extra behavior to specific object instances at runtime, while leaving every other instance of the same class untouched.
  • Subclassing would require one class per combination of optional features, and that number is only going to grow.
  • Different objects need different mixes of the same set of add-on behaviors, decided at runtime rather than fixed in advance.

Real-World Examples

  • **Java I/O streams** - `BufferedInputStream` and `GZIPInputStream` wrap a raw `InputStream` to add buffering or decompression without changing the underlying stream class.
  • **Python function decorators** - `@lru_cache` and `@retry` wrap a function with caching or retry logic while leaving the original function definition untouched.
  • **Express/Koa middleware chains** - each middleware wraps the next handler, layering on logging, authentication, or compression without modifying the route logic itself.