The Decorator Pattern and the Coffee Menu That Exploded
Why writing one class per coffee-and-extras combination blows up fast, and why turning those extras into yes/no switches on the coffee class is just as bad.
Here's the idea in one sentence: instead of writing a new class for every combination of extras, wrap the object in another object that adds just one extra, and stack as many wrappers as you want.
A Coffee Order With Too Many Combinations
Picture a coffee shop menu. There are a couple of base coffees, an espresso and a decaf. And there are extras: caramel, soy milk, whipped cream, mocha, and so on.
The obvious first approach is a class per combination. EspressoWithCaramel, EspressoWithSoy, EspressoWithCaramelAndSoy, DecafWithMocha, and on and on. Any two extras can combine, and any of those combinations can combine with a third.
A handful of extras turns into dozens of classes, each one doing almost nothing on its own.
Turning Extras Into Yes/No Switches
The next idea people usually reach for: make each extra a yes/no switch on the coffee itself. hasMilk, hasSoy, hasCaramel, one flag per extra, all living on the same Beverage class.
This falls apart in a few different ways.
Add a new extra, like chocolate, and you have to open up Beverage and add another flag. That's a class that's supposed to be finished getting edited every time the menu changes.
Not every beverage wants every flag either. Tea inherits from the same Beverage class as coffee, so tea ends up with a hasChocolate flag it can never sensibly answer. A flag that doesn't apply to the thing holding it is a warning sign, not a detail to ignore.
And some extras aren't really yes/no at all. An espresso shot isn't a switch, you can ask for one, two, or three. A boolean can't hold that, so now you need hasSingleShot, hasDoubleShot, hasTripleShot, each one needing to make sure the others are off. Counting turned into a set of flags that have to babysit each other.
Worse, the cost calculation has to know about every single flag to compute a price. One shot costs this much, two shots cost that much, plus two dollars if there's soy, plus caramel, plus whatever else got added since. That logic only grows messier as the menu grows.
The next post fixes both problems at once: no exploding class count, and no flag babysitting either.
The Essentials
- One class per combination of extras multiplies out of control as extras get added.
- Turning extras into yes/no flags on the base class breaks the moment a new extra needs adding, or a flag doesn't apply to every beverage, or an extra needs a count instead of a switch.
Keep reading