just in the process of learning to use the decorator pattern, and ran into a problem which I reckon is simple but I can't seem to find the answer to.
Lets say I have a Beverage class that's abstract. Then lets say I have a few concrete components extending Beverage, americano, espresso, latte etc. And also an abstract Condiment class extending Beverage. Condiments then has multiple subclasses milk, sugar, soy, whip. Each of the condiment subclasses have a cost and getdescription() method inherited from Beverage and Condiments respectively. My question is how when testing, do I stop a certain Beverage instance having more than one condiment of the same type associated with it, i.e an americano getting charged for soy only once,even if soy was stated twice in the test class. I know that I could save a condiment into a list and check if it exists when adding a new condiment, but just wanted to see if a better option existed.
Beverage class
package designpatterns.decorator.coffee;
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
Condiment Decorator
package designpatterns.decorator.coffee;
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
DarkRoast Class
package designpatterns.decorator.coffee;
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "Dark Roast Coffee";
}
public double cost() {
return .99;
}
}
Soy class
package designpatterns.decorator.coffee;
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
public double cost() {
return .15 + beverage.cost();
}
}
If anyone could help or even point me to a good article or tutorial I would greatly appreciate it.
解决方案
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.