这章用星巴克咖啡店的例子演示了装饰器模式的使用。
先来看看在星巴克点咖啡的场景:在星巴克你先要点一种饮料,然后你可以加入各种调料(调料也要钱)。
比如:来一份综合咖啡(House Blend),加一份摩卡,再加一份豆浆。那么一共0.89+0.2+0.15=1.24美元。
在比如:来一份综合咖啡,加两份摩卡(我太喜欢摩卡了),再加一份奶泡。
下面用装饰模式来实现
调料可以看做一种装饰器,装饰在饮料上面。下面是装饰器模式实现星巴克的UML图。
考虑下面的问题:
- 令人奇怪的是为什么调料要继承自饮料,装饰器模式必须要求装饰器是被装饰者的子类。你现在肯定对此很不解,下文贴出了main函数代码,仔细阅读main函数你应该能明白为什么。
- 为什么装饰器要有beverage字段?即,为什么装饰器要记住自己装饰的是哪个对象?
先看看客户端生产咖啡的代码。真的是太巧妙了,你可以任意添加各种调料。
public class Client { public static void main(String[] args) { //综合咖啡 + 摩卡 + 豆浆 Beverage beverage1 = new HouseBlend(); beverage1 = new Mocha(beverage1); //你看,如果调料不是饮料的子类,该语句就出错了 beverage1 = new Soy(beverage1); System.out.println(beverage1.getDescription() + " $:" + beverage1.cost()); //综合咖啡 + 摩卡 + 摩卡 + 奶泡 Beverage beverage2 = new HouseBlend(); beverage2 = new Mocha(beverage2); beverage2 = new Mocha(beverage2); beverage2 = new Whip(beverage2); System.out.println(beverage2.getDescription() + " $:" + beverage2.cost()); } }
输出结果是:
综合咖啡,摩卡 $:1.24
综合咖啡,摩卡 $:1.3900000000000001
实现代码:
//饮料,是抽象类 public abstract class Beverage { protected String description = "不知道是什么饮料"; public String getDescription() { return description; } public abstract double cost(); } //调料(装饰器)。要继承自饮料。虽然调料继承自饮料有点奇怪,但装饰器模式必须要这样。 public abstract class CondimentDecorator extends Beverage { Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } } //综合咖啡,是一种饮料。 public class HouseBlend extends Beverage { public HouseBlend() { description = "综合咖啡"; } @Override public double cost() { return 0.89; } } //摩卡,是一种调料。价格0.2美元。 public class Mocha extends CondimentDecorator { public Mocha(Beverage beverage) { super(beverage); this.beverage.description += ",摩卡"; //此处代码易错,见下面的错误代码演示 } @Override public String getDescription() { return this.beverage.getDescription(); //此处代码易错,见下面的错误代码演示 } @Override public double cost() { return beverage.cost() + 0.2; } }
错误代码演示
//这段代码错在description字段 //Whip自己也从基类继承了description字段 //beverage也有一个description字段 //仔细想想到底应该改变哪个字段的值??? public class Whip extends CondimentDecorator { Beverage beverage; public Whip(Beverage beverage) { this.beverage = beverage; description += ", 奶泡"; } @Override public double cost() { return beverage.cost() + 0.3; } }
//这段代码改进了上面提到的错误,但是还有一处错误,你仔细想想看吧! //Soy从基类继承了getDescription()方法 //beverage也有getDescription()方法 //想想我们到底应该调用谁的getDescription()方法??? //那么看来Soy必须override getDescription()方法,并在其中调用beverage的getDescription()方法。 public class Soy extends CondimentDecorator { Beverage beverage; public Soy(Beverage beverage) { this.beverage = beverage; this.beverage.description += ", 豆浆"; } @Override public double cost() { return beverage.cost() + 0.1; } }
扩展练习
如果想把星巴克的咖啡带回办公室享受,那么需要支付打包费用。因为杯子要钱,杯子还分豪华杯子和简陋的杯子。我也不知道杯子有什么区别,也许豪华的可以保温吧- -。我扩展了下面这个类图,你可以用Java实现一下。
另外,也许有人觉得调料是饮料的子类并不奇怪(也有人把调料当饮料喝)。但是下面这个类图杯子竟然是饮料的子类!!!不要怀疑,装饰器模式就是这样的~~~