装饰器模式的说明:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。原文是:
Attach additional responsibilities to an object dynamically keeping
the same interface.Decorators provide a flexible alternative to
subclassing for extending functionality.
从这段话可以看到装饰器的特点:动态地为对象增加新的功能,即扩展原有对象的功能。
装饰器的优点,至少是相比于继承来说,“更有弹性”,怎么理解呢,装饰器模式相比生成子类更为灵活,简单理解就是继承扩展是静态的,是在编译期间确定的,而装饰器是“动态”的,是在运行时灵活装配出来的。
就以美国电影《钢铁侠》为例,如果托尼·史塔克在没有造出战衣之前,在考虑想拥有一个能够喷火的功能,那么如果采用继承的方式,则需要他生下一个儿子,该儿子通过基因变异出现了喷火功能,如果他又想拥有飞行功能怎么办呢,原来那个儿子的基因已经确定了,不可能拥有飞行的功能了,怎么办呢,只能再生一个儿子,拥有飞行的基因才行,想想都不灵活。这时候装饰模式来解决这个问题,托尼·史塔克不需要“生儿子”动作那么大,只需要打造一个支持扩展功能的钢铁战衣(装饰器)就可以,他自身相当于是具体的待装饰的组件,当需要喷火时,只需要在战衣上增加喷火器就可以,如果又想飞行,只需要在战衣上增加飞行装置就行,当然,如果不想喷火了,拆掉就行了,灵活。
如下图所示为继承图:
如下图所示为装饰器模式图:
按照UML图来展示如下图所示:
代码如下所示:
1.人形接口定义了所有实现人形接口的子类均需要实现action方法。
package com.sxfang.abc;
public interface IPeopleLikeInterface {
public void action();
}
2.TonyStark子类实现了IPeopleLikeInterface接口,定义了TonyStark所具有的行为能力:
package com.sxfang.abc;
public class TonyStark implements IPeopleLikeInterface {
@Override
public void action() {
System.out.println("I am TonyStark");
}
}
3.BattleSuit抽象类,同样实现了IPeopleLikeInterface 接口,与TonyStark不同的是,BattleSuit抽象类还包括一个IPeopleLikeInterface 接口的引用,该引用就是需要被装饰(或被增强)的对象的引用。
package com.sxfang.abc;
public class BattleSuit implements IPeopleLikeInterface {
private IPeopleLikeInterface people;
@Override
public void action() {
}
public IPeopleLikeInterface getPeople() {
return people;
}
public void setPeople(IPeopleLikeInterface people) {
this.people = people;
}
}
4.相应的FireSuit子类,继承自BattleSuit抽象类,实现了自己的action方法,该行为就是增强或装饰的功能。
package com.sxfang.abc;
public class FireSuit extends BattleSuit {
@Override
public void action()
{
super.getPeople().action();
System.out.println("Fire on !!");
}
}
5.与FireSuit子类类似的是可飞行功能增强,如下图所示:
package com.sxfang.abc;
public class FlySuit extends BattleSuit {
@Override
public void action()
{
super.getPeople().action();
System.out.println("Fly !!");
}
}
至此对象间的结构构建完成,那么在实际使用过程中是如何动态增强TonyStark的功能的呢,如下图所示:
public class DecorateRun {
public void main(String[] args)
{
/**
* 初始化一个TonyStark对象
*/
IPeopleLikeInterface tonyStark = new TonyStark();
/**
* 当需要使TonyStark具有喷火功能时
*/
BattleSuit battleSuit = new FireSuit();
battleSuit.setPeople(tonyStark);
/**
* 使用喷火功能
*/
battleSuit.action();
/**
* 当需要使TonyStark具有飞行功能时
*/
BattleSuit battleSuit2 = new FlySuit();
battleSuit.setPeople(tonyStark);
/**
* 使用飞行功能
*/
battleSuit2.action();
}
}
好了,接下来我们看一下成熟的java 源码中流机制是如何使用装饰模式的。
java语言采用流的机制来实现输入/输出。所谓流,就是数据的有序流动,流可以是从某个源出来,到某个目的地去。根据流的方向可以将流分成输出流和输入流。程序通过输入流读取数据,通过输出流写出数据。
例如:一个java程序可以使用FileInputStream类从一个磁盘文件读取数据,如下图:
查看源码可知FileInputStream从磁盘文件中直接读取文件,如下图所示为最基础用法:
FileInputStream inputStream = new FileInputStream("d://1.txt");
int len;
/**
* 一次读取字节,每读取一个字节都要实现一次与硬盘的交互操作
*
*/
while ((len = inputStream.read()) != -1) {
}
当然也可以每次读取更多字节,以减少与硬盘的IO交互次数,如下图所示:
FileInputStream inputStream = new FileInputStream("d://1.txt");
int len;
byte[] bs = new byte[1024];
//这里添加了一个缓存数组,每次从硬盘读取1024个字节,也就是说,每读取1024个字节才与硬盘实现一次交互
while ((len = inputStream.read(bs)) != -1) {
}
实际上java提供了一种更为快速的实现方案,即引入BufferInputStream,如下图所示:
FileInputStream inputStream = new FileInputStream("d://1.txt");
BufferedInputStream bis = new BufferedInputStream(inputStream); //默认有8M的缓存
int len;
byte[] bs = new byte[1024];
/**
* 先从硬盘读出8M到缓存中。然后read,这里的read并不是从硬盘中读取,而是从那8M缓存(内存)中读取,
* 自然要比从硬盘中快得多。8M缓存用完后又会从硬盘补充(也就是说,一次从硬盘获取8M字节的数据)。
* 每8M与硬盘交互一次
*/
while ((len = bis.read(bs)) != -1) {
}
很显然,BufferInputStream就是一个装饰器,FileInputStream一是原始对象,类似于TonyStark的角色,通过BufferInputStream来对FileInputStream的功能进行增强。
其中
ByteArrayInputStream、
FileInputStream、
ObjectInputStream、
PipedInputStream、
SequenceInputStream、
StringBufferInputStream均是原始对象。
FilterInputStream就是抽象的装饰类,
BufferedInputStream、DataInputStream、LineNumberInputStream、PushbackInputStream 均是实现FilterInputStream的具体装饰类,用于装饰或增强原始对象的功能。