The Decorator Pattern and Wrapping the Coffee Instead of Listing It
How wrapping a beverage in a small object that both is a beverage and has a beverage replaces exploding subclasses and babysitting boolean flags.
The last post hit two dead ends: one class per combination of extras, and a pile of boolean flags that need to babysit each other.
Here's the fix, in one sentence: wrap the coffee in a small object that adds one extra, and let that wrapper act exactly like a coffee itself, so you can wrap it again.
A Wrapper That Is the Thing It Wraps
Picture any object with a method called speak() that returns a string. Normally you'd just call speak() and get an answer back.
A decorator sits between you and that object. You call speak() on the decorator instead, the decorator calls speak() on the object it's wrapping, and then it changes the answer before handing it back to you. Nothing about the original object's code ever changes.
Here's the part that trips people up at first: the decorator is the same type as the thing it wraps. Call it two things at once. It is a beverage, so anywhere a beverage is expected, a decorator works too. And it has a beverage, the one it's wrapping, so it can reach inward and build on top of it.
Because a decorator is a beverage, you can wrap a decorator in another decorator. And wrap that in a third. Each layer is a beverage on the outside, and holds one on the inside.
The Add-On Wraps a Beverage
Beverage stays exactly what it was: something with a cost().
class Beverage {
public:
virtual ~Beverage() = default;
virtual int cost() const = 0;
};An add-on decorator is a Beverage (it extends it), and it has a Beverage (it holds one, set through its constructor).
class AddOnDecorator : public Beverage {
public:
explicit AddOnDecorator(Beverage* beverage) : beverage_(beverage) {}
protected:
Beverage* beverage_;
};Notice AddOnDecorator still doesn't implement cost(). It can't; it doesn't know what extra it's adding yet. That's left to whichever concrete decorator, like caramel or soy, actually extends it.
Caramel Adds Two, Then Asks What's Underneath
A real beverage, like an espresso, can report its own cost without asking anyone else.
class Espresso : public Beverage {
public:
int cost() const override { return 1; }
};A decorator can't. Caramel doesn't make sense by itself; nobody orders "just caramel." So its cost() has to ask the beverage it's wrapping for its cost first, then add its own on top.
class Caramel : public AddOnDecorator {
public:
explicit Caramel(Beverage* beverage) : AddOnDecorator(beverage) {}
int cost() const override {
return beverage_->cost() + 2;
}
};beverage_->cost() (or this.beverage.cost()) is the whole trick. Caramel doesn't know or care whether that's a plain Espresso, or another decorator wrapping an Espresso. It just asks, adds two, and hands the total back up.
Wiring It Together
Espresso espresso;
Caramel caramel(&espresso);
std::cout << caramel.cost() << "\n"; // 3caramel.cost() calls espresso.cost(), gets 1, adds 2, returns 3. Add a Soy decorator, shaped exactly like Caramel but adding 1 instead of 2, and wrap it around the caramel-wrapped espresso. soy.cost() asks caramel, caramel asks espresso, espresso answers 1, caramel makes it 3, soy makes it 4. Every layer only ever knows about the one layer directly inside it.
ExpandSoy wraps Caramel wraps Espresso, each layer adding to the cost of the one inside it
The General Shape
Every decorator setup has the same four pieces, no matter what it's decorating.
g++ -std=c++17 main.cpp -o main && ./main (C++) or npm start (TypeScript) to see it run.When This Pattern Actually Earns Its Keep
The coffee example is a good way to see how decorator pattern is wired together. It's a bad example for when to actually reach for it.
Look at what varies between Caramel and Soy: just a number, 2 versus 1. That's a property, not a behavior.
Caramel adds 2, Soy adds 1. Same logic, different number. A plain list of extras, each with its own cost, added up in a loop, solves this with far less machinery.
Java's BufferedInputStream reads in chunks, LineNumberInputStream tracks line numbers, both wrapped around a FileInputStream. Each one does something structurally different.
A real place this pattern earns its keep: deprecating a class without touching every place that uses it. Wrap the old class in a decorator, and let the decorator intercept just the calls you're ready to change, while leaving the rest to fall through untouched. Swap in the decorated version one call site at a time, with the original class never touched at all.
Try It Yourself
A PaymentGateway charges a card. You want to log every charge, amount, timestamp, and whether it succeeded, without editing PaymentGateway itself or every place that calls it.
- Build
LoggingPaymentGateway, a decorator that is aPaymentGatewayand has one. - Its
charge(amount)should print a log line, call the real gateway'scharge(amount), print the result, then return it. - Swap
new PaymentGateway()fornew LoggingPaymentGateway(new PaymentGateway())at exactly one call site, and confirm every other call site still works untouched. - Now add a
RetryingPaymentGatewaydecorator that retries a failed charge once before giving up, and wrap it around the logging one. Neither decorator should need to know the other exists.
The Essentials
- A decorator is the thing it wraps (so it can stand in for it anywhere) and has the thing it wraps (so it can build on top of it).
- Each decorator's method calls the same method on what it wraps, then adds its own piece. That chain ends at a real, standalone object.
- This pattern pays off when decorators genuinely differ in behavior, not just in a number or a name. When the only difference is a property, a plain list is usually simpler.
Keep reading