装饰器是一个扩展另一个类的功能的类。装饰器通常实现相同的接口,以便可以使用装饰对象而不是基本对象。一个很好的例子是压缩器和/或加密器应用于文件或更一般地应用于数据流实现,如@nits.kk 的the answer 所示。
以披萨为例,我们应该定义我们需要的行为:
public interface Pizza {
public String getIngredients(); // comma separated
public double getTotalPrice();
}
比萨饼由两种主要成分组成:强制性的单一基料和可选的多种配料。每种成分都有自己的价格。
public class PizzaIngredient {
private double getPrice() {
return 0.0;
}
}
pizza base 本身就是一个最简单的pizza,所以它必须实现Pizza 接口。它有一个大小作为它的属性(当然还有价格)。我们可以将 size 实现为一个单独的类,但我认为它不合理 - 它不够通用,无法在比萨世界之外有用,也不够复杂,不值得拥有自己的接口。
public class PizzaBase extends PizzaIngredient implements Pizza {
public PizzaBase(String size) {
this.size = size;
}
public String getIngredients() {
return size + " base"; // the only ingredient is this base
}
public double getTotalPrice() {
return getPrice(); // the base-only pizza costs the base cost
}
private double getPrice() {
if(size == "small")
return 2.0;
if(size == "medium")
return 2.5;
return 3.0; // large and undefined
}
private final String size;
}
现在我们需要浇头。它们将作为装饰器添加到披萨上:披萨加上配料也是披萨,所以最上面的配料将代表整个组合。这种比萨的配料表是底层比萨的配料表加上其最顶层配料的名称。同样是总价。
public class PizzaTopping extends PizzaIngredient implements Pizza {
public PizzaTopping(String name, Pizza pizza) {
this.name = name;
this.pizza = pizza;
}
public String getIngredients() {
return pizza.getIngredients() + ", " + getName();
}
public double getTotalPrice() {
return pizza.getTotalPrice() + getPrice();
}
public String getName() {
return name;
}
private final String name;
private final Pizza pizza;
}
让我们定义一些具体的浇头:
public class MozzarellaTopping extends PizzaTopping {
public MozzarellaTopping(Pizza pizza) {
super("mozzarella", pizza);
}
private double getPrice() {
return 0.5;
}
}
public class MushroomTopping extends PizzaTopping {
public MushroomTopping(Pizza pizza) {
super("mushroom", pizza);
}
private double getPrice() {
return 2.0;
}
}
public class PepperoniTopping extends PizzaTopping {
public PepperoniTopping(Pizza pizza) {
super("pepperoni", pizza);
}
private double getPrice() {
return 1.5;
}
}
public class GreenOliveTopping extends PizzaTopping {
public GreenOliveTopping(Pizza pizza) {
super("green olive", pizza);
}
private double getPrice() {
return 1.2;
}
}
好的,有很多课程;但我们需要哪一个?什么时候需要?
这里有一家工厂加入了团队。工厂是创建某些类的对象的类。它用于隐藏幕后的创建细节,特别是当创建的对象很复杂或属于不同的具体类时。当生成的对象被创建为独立实体时,工厂类可以只是一个名称空间,其中包含一个静态方法。 OTOH,如果对象是在某些上下文中创建的,则工厂可能是与上下文相关联的对象(例如,参数化)并在创建过程中使用该上下文。
我们可以根据用户的输入,随意使用工厂来制作比萨配料。大多数配料都是浇头,必须涂在现有的比萨饼上,所以让我们把比萨饼送到工厂,以接收装饰好的比萨饼。特殊情况是创建一个比萨饼底,它不适用于另一个比萨饼;在这种情况下,pizza 参数被忽略,所以我们可以传递null。
public class PizzaFactory {
public static Pizza getPizza(Pizza pizza, String name)
{
if ( name.equals("small") || name.equals("medium") || name.equals("large") )
return new PizzaBase(name);
else if ( name.equals("mozzarella") )
return new MozzarellaTopping(pizza); // add topping to the pizza
else if ( name.equals("mushroom") )
return new MushroomTopping(pizza);
else if ( name.equals("pepperoni") )
return new PepperoniTopping(pizza);
else if ( name.equals("green olive") )
return new GreenOliveTopping(pizza);
return null;
}
}
现在我们已经准备好制作我们的披萨了。
class PizzaTest {
public static void main(String[] args) {
DecimalFormat priceFormat = new DecimalFormat("#.##");
Pizza pizza;
pizza = PizzaFactory.getPizza(null, "small");
System.out.println("The small pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
pizza = PizzaFactory.getPizza(null, "medium");
pizza = PizzaFactory.getPizza(pizza, "mozzarella");
pizza = PizzaFactory.getPizza(pizza, "green olive");
System.out.println("The medium pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
String largePizzaOrder[] = { "large", "mozzarella", "pepperoni",
"mushroom", "mozzarella", "green olive" };
pizza = null;
for (String cmd : largePizzaOrder)
pizza = PizzaFactory.getPizza(pizza, cmd);
System.out.println("The large pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
}
}
警告:以上代码存在一些陷阱和捷径。
最重要的是缺少输入验证:当意外命令到达时,工厂将返回null,这将导致将来使用getIngredients()或getTotalCost()时崩溃。
另一个是将价格硬编码到具体的类中。一个实际的解决方案必须使用一些价目表并在成分创建(并将获取的价格存储在成分对象中)或使用时获取价格,即在 getCost() 方法中(这需要从比萨的配料)。