装饰者模式动态地将责任附加到对象杭,若要拓展功能,装设置提供了比继承更有弹性的替代方案。
星巴兹有多种咖啡,它们具有不同的价格。在购买咖啡时,也可以要求在其中加入各种调料,例如豆浆、摩卡、奶泡等等。需要根据所加入的调料收取不同的费用。
这里运用”装饰者模式“,以饮料为主体,然后在运行时以调料来“装饰”饮料,比如说顾客想要摩卡和奶泡深焙咖啡,那么
- 哪一个深焙咖啡(DarkRoast)对象
- 以摩卡对象装饰它
- 以奶泡对象装饰它
- 调用cost()方法,并依赖委托(delegate)将调料的价格加上去。
看一下装饰者模式的类图是如何实现的:
- ConcreteComponent是我们要动态地加上新行为的对象
- 每个Component都可以单独地使用,或者被装饰者包起来用
- 每个装饰者都包装一个组件,也就是说,装饰者有一个实例变量以保存某个Component的应用。
- Decorator是装饰者共同实现的接口。
- ConcreteDecorator有一个实例变量,可以记录所装饰的食物。
所以实现的类图就变成:
代码如下:
Beverage,是基类,实现了getDescription(),同时cost是抽象方法。由子类去实现。
1 package headfirst.decorator.starbuzz; 2 3 public abstract class Beverage { 4 String description = "Unknown Beverage"; 5 6 public String getDescription() { 7 return description; 8 } 9 10 public abstract double cost(); 11 }
四种咖啡:
HouseBlend,DarkRoast,Espresso,Decaf
1 package headfirst.decorator.starbuzz; 2 3 public class HouseBlend extends Beverage { 4 public HouseBlend() { 5 description = "House Blend Coffee"; 6 } 7 8 public double cost() { 9 return .89; 10 } 11 } 12 13 public class DarkRoast extends Beverage { 14 public DarkRoast() { 15 description = "Dark Roast Coffee"; 16 } 17 18 public double cost() { 19 return .99; 20 } 21 } 22 23 public class Espresso extends Beverage { 24 25 public Espresso() { 26 description = "Espresso"; 27 } 28 29 public double cost() { 30 return 1.99; 31 } 32 } 33 34 public class Decaf extends Beverage { 35 public Decaf() { 36 description = "Decaf Coffee"; 37 } 38 39 public double cost() { 40 return 1.05; 41 } 42 }
接下来是调料接口,或者是抽象类。它必须继承我们的Beverage。并且声明了getDescription()方法。
1 package headfirst.decorator.starbuzz; 2 3 public abstract class CondimentDecorator extends Beverage { 4 public abstract String getDescription(); 5 }
接下来是四种调料:
1 package headfirst.decorator.starbuzz; 2 3 public class Mocha extends CondimentDecorator { 4 Beverage beverage; 5 6 //保存了一个Berverage对象 7 public Mocha(Beverage beverage) { 8 this.beverage = beverage; 9 } 10 11 public String getDescription() { 12 return beverage.getDescription() + ", Mocha"; 13 } 14 15 public double cost() { 16 //注意这里委托了beverage.cost() 17 return .20 + beverage.cost(); 18 } 19 } 20 21 public class Whip extends CondimentDecorator { 22 Beverage beverage; 23 24 public Whip(Beverage beverage) { 25 this.beverage = beverage; 26 } 27 28 public String getDescription() { 29 return beverage.getDescription() + ", Whip"; 30 } 31 32 public double cost() { 33 return .10 + beverage.cost(); 34 } 35 } 36 37 public class Soy extends CondimentDecorator { 38 Beverage beverage; 39 40 public Soy(Beverage beverage) { 41 this.beverage = beverage; 42 } 43 44 public String getDescription() { 45 return beverage.getDescription() + ", Soy"; 46 } 47 48 public double cost() { 49 return .15 + beverage.cost(); 50 } 51 } 52 53 public class Milk extends CondimentDecorator { 54 Beverage beverage; 55 56 public Milk(Beverage beverage) { 57 this.beverage = beverage; 58 } 59 60 public String getDescription() { 61 return beverage.getDescription() + ", Milk"; 62 } 63 64 public double cost() { 65 return .10 + beverage.cost(); 66 } 67 }
最后是测试代码:
1 package headfirst.decorator.starbuzz; 2 3 public class StarbuzzCoffee { 4 5 public static void main(String args[]) { 6 Beverage beverage = new Espresso(); 7 System.out.println(beverage.getDescription() 8 + " $" + beverage.cost()); 9 10 Beverage beverage2 = new DarkRoast(); 11 beverage2 = new Mocha(beverage2); 12 beverage2 = new Mocha(beverage2); 13 beverage2 = new Whip(beverage2); 14 System.out.println(beverage2.getDescription() 15 + " $" + beverage2.cost()); 16 17 Beverage beverage3 = new HouseBlend(); 18 beverage3 = new Soy(beverage3); 19 beverage3 = new Mocha(beverage3); 20 beverage3 = new Whip(beverage3); 21 System.out.println(beverage3.getDescription() 22 + " $" + beverage3.cost()); 23 } 24 }
那如果我们这时产生了新的需求,要求在菜单上加上咖啡的容量的大小,供顾客选择大杯,小杯,中杯,那该怎么办?要注意,大杯的饮料比较贵,同时它加的调料也要比较多,所以调料的价格也不一样。
这时我们应该在Beverage中定义size和getSize()的函数,并且在四种饮料中要根据size的大小,cost()函数要返回不同的价格。
在调料中,我们也需要获取被装饰者的size,然后cost函数加上对应调料的价格。
1 package headfirst.decorator.starbuzz; 2 3 public class Soy extends CondimentDecorator { 4 Beverage beverage; 5 6 public Soy(Beverage beverage) { 7 this.beverage = beverage; 8 } 9 10 public String getDescription() { 11 return beverage.getDescription() + ", Soy"; 12 } 13 14 public int getSize(){ 15 return beverage.getSize(); 16 } 17 18 public double cost() { 19 double cost = beverage.cost(); 20 if (getSize() == Beverage.TALL){ 21 cost += .10; 22 }else if (getSize() == Beverage.GRANDE){ 23 cost += .15; 24 }else if (getSize() == Beverage.VENTI){ 25 COST += .20; 26 } 27 return cost; 28 } 29 }
最后要说的就是java.io包中,定义了很多类,就是装饰者模式的实现。我们可以看看InputStream的实现。
OutputStream和Reader,Writer的设计大致差不多。