上图为装饰模式的UML图
思想
当前有个需求如下:一家奶茶店出售各式饮料,比如饮料A,饮料B,饮料可以放入不同的配料,比如加冰,加巧克力,加摩卡等等。要如何实现呢。是否可以通过继承来实现?比如实现巧克力饮料A,摩卡饮料B,加冰饮料A。。。。。。可以想见,这样会出现类爆炸的情况,如果客人需要双倍加冰双倍摩卡饮料A怎么办?如果后期饮料或是配料增加了种类,又会增加一大波类出来。
那么,如果在饮料中添加各个配料标志位来表示是否需要添加该配料的情况怎么样?这样乍看很合适,但是当增加配料的时候,如果饮料的种类很多,那么对原来设计的修改就比较麻烦了。
就没有一种情况,可以在不修改原来的代码,并且还能在饮料和配料增加的情况还能简便的扩展的方法么?那就用到今天的主角装饰模式了。
装饰模式的核心在于装饰类和被装饰类具有共同的基类。这样,装饰者就可以在原来的基类上包装一下,看上去多了一些功能或操作。特别要注意,饮料和配料类都继承自同一个类,在创建配料对象时,需要传入饮料对象作为参数,配料在饮料对象上进行进一步封装。
卖咖啡实现
Beverage.Java
package coffee; public abstract class Beverage { //抽象组件 String description = "Unknown Beverage"; public String getDescription(){ return description; } public abstract double cost(); }
CondimentDecorator.Java
package coffee; public abstract class CondimentDecorator extends Beverage{ //抽象装饰类 public abstract String getDescription(); }
Espresso.Java
package coffee; public class Espresso extends Beverage{ //具体组件类 public Espresso() { description = "Espresso"; } @Override public double cost() { // TODO Auto-generated method stub return 1.99; } }
Mocha.Java
package mocha; import coffee.Beverage; import coffee.CondimentDecorator; public class Mocha extends CondimentDecorator{ //具体装饰类 Beverage beverage; public Mocha(Beverage beverage) { // TODO Auto-generated constructor stub this.beverage = beverage; } @Override public String getDescription() { // TODO Auto-generated method stub return beverage.getDescription() + ", Mocha"; } @Override public double cost() { // TODO Auto-generated method stub return .20 + beverage.cost(); } }
Test.Java
package test; import coffee.Beverage; import coffee.Espresso; import coffee.HouseBlend; import mocha.Mocha; public class Test { public static void main(String[] args) { // TODO Auto-generated method stub Beverage beverage = new Espresso(); System.out.println(beverage.getDescription() + " $" + beverage.cost()); beverage = new Mocha(beverage); System.out.println(beverage.getDescription() + " $" + beverage.cost()); } }
结果
总结
现在,我们回过头来看,不修改原有代码的前提下,可以新增功能么?
当然可以,如果新加了饮料,那么我们就新加一个Beverage的实现类;如果新加一个配料,那么我们就新加一个Condiment实现类;要添加各种配料,只需要拿到IBeverage的实现类实例再包装一下即可,而且最神奇的地方在于原来的结构完全没有修改。
装饰模式高度契合对修改关闭对扩展。然而装饰模式也有它的缺点。
首先 ,装饰模式对类型的控制很重要,比如在被装饰类饮料中有个方法,但是使用装饰类配料去包装之后,所得到的已经不再是饮料的引用了,它已经变成配料的引用了,此时如果调用被装饰者饮料的方法是不能调用的,强制类型转换也无法实现。因此,装饰模式最后一个包装的类型决定了它能调用什么方法。
另外,装饰器模式中装饰者和被装饰者之间的关系界限不是很明显,经过一通包装之后,我们只能看到最外面的一层对象,至于里面的核心是什么对象,一共包裹了几层包装,比较难看出来。前面我在Condiment2和Condiment3中另外添加了mthodOf方法,如果最后包装的是Condiment3,那么我只能调用到mthodOf3方法,至于里面一层的包装的方法,是无法调用到的。
应用
IO流就是用到装饰者模式