前言
当我们初学编程时,扩展程序功能一般习惯使用继承,使用继承有一些缺点,那就是容易造成类爆炸,并且容易继承一些不需要的特性。当我们学习完装饰器模式后,会发现善用组合会有比继承更好的效果。
正文
1、咖啡馆案例
我们还是来看《Head First设计模式》给出的一个实际场景,一个咖啡馆要做一个订单系统。这个咖啡馆有4款基础咖啡(HouseBlend、DarkRoast、Decaf、Espresso),在基础咖啡之上我们可以加3种调料(Milk、Soy、Mocha),客人可以随意组合1款基础咖啡和多种调料,最后计算出总价。
最笨的办法就是穷举基础咖啡和调料的组合,光是一种基础咖啡加一种调料就要实现4*3=12个子类。这是典型的类爆炸。而且极不容易扩展,想象一下如果要再加一种基础咖啡或调料,又要新增多少个类。
再想一种办法,我们将是否加料放在父类Beverage中,将各种调料的布尔值放在父类中,于是父类中的cost方法要加上各种判断,最终算出调料总价。基础咖啡子类继承父类,cost方法将基础咖啡的价钱加上父类中的调料价钱,算出总价。这种方式的弊端是什么呢?调料价钱的改变或新减调料都会修改现有代码,而且有的基础咖啡并不需要继承所有调料,而且如果想要双倍调料怎么办?
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。但是,如果使用组合的方式扩展对象的行为,就可以在运行时动态的扩展。通过动态的组合对象,可以写新的代码添加新的功能,无需修改现有的代码。既然没有修改现有代码,那么引入bug的机会将大大减少。
设计原则:类应该对扩展开发,对修改关闭
2、用装饰者模式改造案例
我们以基础饮料为主体,然后在运行时以调料来“装饰”饮料。比如顾客想要摩卡和奶泡深焙咖啡,那么要做以下步骤:
a、拿一个深焙咖啡DarkRoast对象;
b、以摩卡Mocha对象装饰它;
c、以奶泡Whip对象装饰它;
d、调用cost方法,并委托每一层装饰器和主题计算总价。
于是我们可以得到装饰器模式的定义:动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
- 装饰者和被装饰者有相同的超类;
- 可以用一个或多个装饰器包装一个对象;
- 既然有相同的超类型,那么在任何需要被装饰对象的场合,都可以用装饰器来代替;
- 装饰器可以在被装饰者的行为前后加上自己的行为,以达到特定的目的;
- 可以在运行时动态地,不加限制的使用装饰器来装饰对象;
按照上面装饰器的类图,我们来重新画一下咖啡馆应用的类图:
我们可以在装饰器的父类中持有Beverage对象,也可以在具体的装饰器对象中持有Beverage对象,本例中是在具体子类中:
Public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
public double cost() {
return 0.2 + beverage.cost();
}
}
按照类图写完代码后,让我们真正来点一杯加料的咖啡吧。
public static void main(String args[]) {
Beverage beverage = new DarkRoast() // 先制造一个基础咖啡
beverage = new Mocha(beverage); // 加一分Mocha
beverage = new Mocha(beverage); // 再加一分Mocha
beverage = new Whip(beverage); // 加一分Whip
sout(beverage.cost()); // 计算总价
}
3、装饰者模式在Java I/O中的应用
java.io包中的类简直是多如牛毛,初学者根本无从下手,但是我们已经学习了装饰者模式,那就来抽丝剥茧,捋一捋这些类的关系。IO体系只提供了几个基础类提供了最基本的字节读写功能,比如FileInputStream、StringBufferInputStream、ByteArrayInputStream。其他都是这些基础类的装饰器,也就是在这些基础类上额外增加了一些功能。
那么,装饰器的抽象类是谁呢?那就是下图中的FilterInputStream,我们可以继承这个类实现自己的装饰器。我们看到FilterInputStream中持有了一个InputStream类型的引用。
public class FilterInputStream extends InputStream {
/**
* The input stream to be filtered.
*/
protected volatile InputStream in;
...
}
总结
装饰者模式适用于这样一种场景:有一些类提供基础的功能,还有一些类要增强这些基础类的功能。于是我们将增强功能类叫做装饰者,装饰者持有一个基础类型的引用,利用组合的形式对基础类进行功能增强。