0 XP · 0 scenes

Decorator

Add features to an object by wrapping it
The problem

Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndCream... Every combination needs a subclass.

The idea

Make each extra a wrapper with the same interface as the thing it wraps.

Reach for it when

You need many optional extras that combine freely

1

The picture from life

A plain coffee costs 50. You ask for milk, then sugar. The barista does not invent a new drink for each mix; he wraps your coffee with each add-on, and every layer adds its own price and its own note on the cup. Layers can be stacked in any order, any number of times.

2

The problem

What goes wrong when you do not use it.

Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, CoffeeWithMilkSugarAndCream... Every combination needs a subclass. Five add-ons can mean thirty-two classes.

✗ Before
CoffeeCoffeeWithMilkCoffeeWithSugarCoffeeWithMilkAndSugar… and so on
  • extends (is a)
Every combination of extras needs its own subclass. Three extras means eight classes.
Reach for it when
  • You need many optional extras that combine freely
  • You cannot (or should not) subclass
  • Extras should be added or removed at run time
3

The idea

How the pattern fixes it.

Make each extra a wrapper with the same interface as the thing it wraps. It forwards calls to the inner object and adds its own bit before or after. Wrap again to add another extra.

✓ After
«interface»Drinkcost()label()Coffeecost() → 50MilkDecoratorinner: Drinkcost() → inner + 10SugarDecoratorinner: Drinkcost() → inner + 5
  • implements
Small wrappers that each add one thing. Stack them in any combination.

Who is who

Drink (interface)
cost() and label().
Coffee
The plain thing being decorated.
MilkDecorator, SugarDecorator
Hold a Drink, add price and label, forward the rest.

Build it in steps

  1. 1Define the common interface.
  2. 2Write the plain object.
  3. 3Write a decorator that holds the interface and implements it.
  4. 4Wrap objects in any order you like.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientSugarMilkCoffee1cost()2cost()3cost()450550 + 10 = 60660 + 5 = 65
Step 1/6

The drink is Sugar(Milk(Coffee)). The client asks the outer wrapper.

cost()
5

The code

Python and Java. Try writing the classes yourself from the diagram first.

class Coffee:
    def cost(self):
        return 50

    def label(self):
        return "coffee"


class MilkDecorator:
    def __init__(self, inner):
        self.inner = inner                    # same interface as what it wraps

    def cost(self):
        return self.inner.cost() + 10

    def label(self):
        return self.inner.label() + " + milk"


class SugarDecorator:
    def __init__(self, inner):
        self.inner = inner

    def cost(self):
        return self.inner.cost() + 5

    def label(self):
        return self.inner.label() + " + sugar"


def demo():
    drink = SugarDecorator(MilkDecorator(Coffee()))
    return drink.cost(), drink.label()

What you gain

  • Combine extras freely, no subclass explosion
  • Add or drop extras at run time
  • Each extra does one thing

What it costs

  • Many small wrapper objects
  • Order can matter and is easy to get wrong
  • Debugging through layers is harder

You have already met it in

  • Java I/O: new BufferedReader(new InputStreamReader(...))
  • Python decorators (@cache, @login_required) follow the same wrap idea
  • Middleware stacks in web frameworks

Often mixed up with

  • ProxyA Proxy usually controls access to its object; a Decorator adds behaviour and can be stacked.
  • CompositeComposite has many children; a Decorator wraps exactly one.
  • StrategyStrategy changes how the object works inside; Decorator changes what it does from outside.
6

Make it stick

Build a TextSource with read(), then decorators for Uppercase, Trim and Logging. Stack them in two different orders and see how the result changes.

Confidence