Sounds like the example from Head First Design Patterns (HFDP)? The test case is simple to understand, but the way to do may not be so much.
Think of Decorators as Wrappers. When a decorator is about to wrap something, it could check that "thing" to see if it contains already a decorator of its own type. Here's code from HFDP that I changed slightly:
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Soy(beverage2); // wrap once
beverage2 = new Soy(beverage2); // wrap again (**error case)
You'd have to decide if you want to disallow multiple wrapping for all decorators, or perhaps some decorators can have a kind of "once only" attribute. Another thing to decide is whether to fail (throw an exception) if a second wrap occurs (the ** in the comment above) or if you want to just ignore the extra wraps in cost()
.
It's probably cleaner and less bug-prone if you stop multiple wrapping at wrap-time. That would be in the constructor. You could code a general function in the abstract class that checks this using reflection (won't work in languages that don't support it), or parses the descriptions of the wrapped object to find its own string (less reliable if decorations don't have unique names).
The biggest problem I see with doing this is that Condiments wrap Beverages, and by design (information hiding), condiments don't "know" they're wrapping other condiments. Any code you write will be possibly fragile (it might violate the open-closed principle). Such are the trade-offs in design, however. You can't have everything, so decide what's more important (stopping multiple wraps, or having a design that allows adding new decorators without breaking anything).
Using the getDescription (parsing it) would make the most sense, probably, provided you can rely on the format to identify nestings.
The Soy class could do this:
private String myDescription = "Soy"
public Soy(Beverage beverage) {
if (beverage.getDescription().contains(myDescription)) {
throw new Exception();
}
this.beverage = beverage;
}
But a better way might be to .split()
on the "," character and check those strings, since descriptions are just concatenations using commas (in getDescription()
).
As I said, if it's a general rule to disallow all multiple condiment wraps, you could refactor this logic up to the CondimentDecorator class to avoid duplicated code. You could even have Decorator boolean attribute to say "allowsMultiple" and code that.