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.

September 11, 20267 min read9 / 19

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"; // 3

caramel.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.

Soy wraps Caramel wraps Espresso, each layer adding to the cost of the one 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.

A component
The shape everything shares. Here, that's `Beverage`.
A concrete component
Something that stands on its own. Here, `Espresso`.
A decorator
Is a component, and has one. Here, `AddOnDecorator`.
A concrete decorator
Adds one specific thing on top. Here, `Caramel`, `Soy`, and any other extra.
Run this yourself: All code examples in this post are in the code-practice repo. Clone it, then run 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.

✕
Varies by property

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.

✓
Varies by behavior

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.

  1. Build LoggingPaymentGateway, a decorator that is a PaymentGateway and has one.
  2. Its charge(amount) should print a log line, call the real gateway's charge(amount), print the result, then return it.
  3. Swap new PaymentGateway() for new LoggingPaymentGateway(new PaymentGateway()) at exactly one call site, and confirm every other call site still works untouched.
  4. Now add a RetryingPaymentGateway decorator 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

  1. 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).
  2. 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.
  3. 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.