这篇博客记录一下装饰者模式。
我们首先借用一下Head First中的样例。来看看装饰者模式涉及的应用场景。
假设我们须要开发一个饮料计费系统,例如以下图所看到的。
Beverage作为全部饮料的父类(抽象类或接口均可),
定义了一个cost方法,用于计算饮料的价格。
起初定义了四种主要的饮料,HouseBlend、DarkRoast、Decaf和Espresso。
这些饮料均继承Beverage。并实现各自的cost方法。
在上文的场景下,假设客户在购买饮料时,
能够选择性地向基本饮料中加入不同的调料。
基本饮料加入调料后。就变成了一种“新”的饮料,
须要又一次实现cost方法。
假设我们利用继承的方式。实现这些“新”的饮料,
那么整个计费系统的类图将变成例如以下的结构:
如上图所看到的,我们仅加入了两种调料milk和tea。
但整个设计体系中立刻新增了很多子类。
easy预见。随着基本饮料和调料种类的添加。
这些子类的数量会进一步增多。达到一个无法维护的数量。
试想一下,假设某个基本饮料或调料的价格发生改变,
那么就有很多类涉及的代码须要调整。维护这样一种代码。
无疑是程序猿的噩梦。
针对这样的问题。有的朋友可能会这么解决:
在父类Beverage中添加标志位。来表示是否加入了某种调料。
同一时候。添加相应的设置和推断接口。
这么一来,子类在计算价格时。就能够通过父类的接口,
推断是否加入了某种饮料。然后依据推断结果来计算价格。
通过这样的方式,就能够大量地降低子类的数量,
整个设计结构清晰易懂。
然而。这么设计也有一个致命的缺陷。
假设新增了调料的种类,那么每一个子类的cost方法还是须要重写。
同一时候,随着调料种类的添加,子类的cost方法中,
必定存在大量用于推断是否含有某种饮料的推断语句。
从总体来看,这样的设计方式还是不够优雅,
违背了对扩展开发,对改动关闭的设计原则。
为了解决这类问题,就须要使用本篇博客的主角装饰者模式了。
整个装饰者的设计思路基本上能够用下图表示:
如上图所看到的。假设我们须要一个加了Milk和Tea的HouseBlend。
那么我们能够建立Milk和Tea的类,继承自Beverage。
在代码执行时。我们能够动态地用Tea来包装HouseBlend,得到的一个Beverage对象;
然后。继续用Milk来包装这个新的Beverage对象,得到终于的Beverage。
此时。Milk和Tea类就能够看作HouseBlend的装饰者对象。
在计算总体的价格时,我们能够直接调用终于的Beverage的cost接口。
我们已经知道,最外层的实际上是个装饰者对象。
于是。装饰者对象会进一步调用其持有的Beverage对象的cost接口。
假设下一个Beverage对象,仍然是个装饰者,
那么它会进一步调用其持有的Beverage对象的cost接口。
通过如图所看到的的递归调用,最后将调用到基本饮料的cost接口,
得到基本饮料的价格。
然后,在基本饮料价格的基础上。
逐步添加调料本身的价格,就能够得到终于的价格。
通过这样的方式,不论调料怎样改变,我们都easy写出清晰简单的代码。
如今。是时候来看看装饰者模式的定义和结构图了。
装饰者模式动态地将责任附加到对象上。
若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
上图的Component是被装饰对象的父类。
ConcreteComponent是实际的被装饰对象。
Decorator是装饰对象的父类或共同接口;
ConcreteDecoratorA和ConcreteDecoratorB是实际的装饰者对象,
这些对象将持有Component对象的引用,同一时候作为Component的子类。
相应装饰者模式的结构图。我们看看上述场景改良后的设计结构:
对照上文的装饰者模式结构图,新的设计应该是比較easy理解的。
在本文的最后,我们看看装饰者模式的一个实际应用场景,
Java IO中输入流设计结构:
在了解装饰者模式后,再看以下的代码,是不是easy理解的多:
.............
InputStream in = new BufferedInputStream (
new FileInputStream("test.txt"));
.............