设计模式之装饰者模式(一)
现在有一家咖啡厅,里面有四种不同的咖啡种类,而每种不同的咖啡都可以附加牛奶、豆浆、奶泡或者摩卡,而且每次客人点餐,还可以附加多倍的某种配料,比如双倍摩卡。如果使用简单的类继承方法,就要写数量庞大的类,而且很难管理类似于“双倍摩卡”这种形式。
于是,我们就可以引入装饰模式,装饰模式动态地将责任附加到对象上,扩展功能时,提供了比继承更有弹性的替代方案,
装饰模式的类图如下:
装饰模式的特点是
1.装饰者必须能取代被装饰者,也就是说装饰者和被装饰者要用共同的父类。上图的ConcreteComponent类就是被装饰者,ConcreteDecoratorA和B就是装饰者
2.装饰者中含有一个其父类的引用,用来表明被装饰者或者其他装饰者。在装饰者的方法中,往往要先调用这个引用的同名方法。
比如,现在我要一杯摩卡奶泡深度烘焙咖啡,计算价格的过程应该是这样的。
Whip中保存的是Mocha的引用,Mocha中保存的是DarkRoast(被装饰者)的引用,通过循环调用,最后就计算出了价格。
一开始我的疑问是,为什么装饰者必须能取代被装饰者,如果被装饰者和装饰者的父类不一样,会怎么样呢?
思考了以下以后发现,如果被装饰者和装饰者的父类不一样,这意味着装饰者内部必须还要保存一个被装饰者的引用,而假如需要双重和多重的装饰者,则多个装饰者无法联系在一起,被装饰者也不知道其装饰者的存在。
代码参考如下:
Beverage类
/** 饮料类,是装饰者和被装饰者都继承的类。 **/ public abstract class Beverage { String description="unknown"; public String getDescription() { return description; } public abstract double cost(); }
CondimentDecorator类
/** 装饰者类,所有装饰者都继承自这个类 **/ public abstract class CondimentDecorator extends Beverage { public abstract String getDescription(); }
DarkRoast类
/** 被装饰者类,表示其中一种咖啡(深度烘焙) **/ public class DarkRoast extends Beverage { public DarkRoast() { this.description="DarkRoast"; } public double cost() { return 0.99; } }
还有其他的被装饰者类,Decas,Espresso, HouseBlend的代码省略
Milk类
/** 装饰者,继承了CondimentDecorator,表示其中一个装饰者 **/ public class Milk extends CondimentDecorator { Beverage beverage;//用一个实例变量记录被装饰者 public Milk(Beverage beverage)//通过构造方法把被装饰者的引用传递过来 { this.beverage=beverage; } public String getDescription() { return beverage.getDescription()+",Milk"; } public double cost()//先计算被装饰对象的价钱,再加上该装饰品的价钱。 { return 0.10+beverage.cost(); } }
其他的装饰者类,Mocha,Soy,Whip的代码省略
测试类
package com.qingfei.decorator; public class Test { public static void main(String[] args) { Beverage beverage=new Espresso(); System.out.println(beverage.getDescription()+"$"+beverage.cost()); Beverage beverage2=new DarkRoast(); beverage2=new Mocha(beverage2); beverage2=new Whip(beverage2); beverage2=new Soy(beverage2); System.out.println(beverage2.getDescription()+"$"+beverage2.cost()); } }
输出结果是
这样一来,就可以动态地为被装饰者添加装饰者了,当要扩展的时候,只需要增加一个装饰者的类即可,而装饰者类的修改对于被装饰者类没有影响,符合了开闭原则。