装饰(Decorator)模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式使用原来被装饰的类的一个子类的实例,把客户端的调用委派到被装饰类。关键在于这种扩展是完全透明的。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。
我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。
模式的类图如下:
在装饰模式中的各个角色有:
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附件责任的对象。
- 具体构件(Concrete Component)角色:定义一个要接收附加责任的类。
- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Concrete Decorator)角色:负责给构件对象“贴上”附加的责任。
Component为组件和装饰的公共父类,它定义了子类必须实现的方法。
ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。
Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。
ConcreteDecorator1和ConcreteDecorator2是具体的装饰,可以使用它们来装饰具体的Component。
Java I/O库的两个设计模式:
Java的I/O库总体设计是符合装饰者模式(Decorator)跟适配器模式(Adapter)的。如前所述,这个库中处理流的类叫做流类。引子里所谈到的FileInputStream,FileOutputStream,DataInputStream及DataOutputStream都是流处理器的例子。
1 装饰者模式:在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器可以对另一些流处理器起到装饰作用,形成新的,具有改善了的功能的流处理器。装饰者模式是Java I/O库的整体设计模式。这样的一个原则是符合装饰者模式的,如下图所示:
2 适配器模式:在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器是对其它类型的流源的适配。这就是适配器模式的应用,如下图所示。
适配器模式应用到了原始流处理器的设计上面,构成了I/O库所有流处理器的起点。
三 装饰模式的应用
学过装饰模式后,大家会发现,它在Java语言中最著名的应用莫过于Java I/O标准为库的设计了。这一节将以处理Byte流为例,看看装饰模式是怎样得到应用的。
为什么不用继承而用装饰模式
我们知道Java I/O库需要很多性能的各种组合,如果说这些性能的组合是通过继承方式来实现的话,那么每一种组合都需要一个类,这样就会出现大量重复性问题的出现,从而使类数目“爆炸”。而如果采用装饰模式,那么不仅类的数目大减少了,性能的重复也可以减至到最少。所以装饰模式是Java I/O库的基本模式。在这里我想再用<<Head First Design Pattern>>中讲到装饰模式时候的一个例子,看看装饰模式是怎么达到不仅类的数目大减少了,性能的重复也可以减至到最少。
再回到Java I/O库,由于装饰模式的引用,造成了灵活性和复杂都大大增加了,我们在使用Java I/O库时,必须理解Java I/O库是由一些基本的原始流处理器和围绕它们的装饰流处理器所组成的,这样可以在学习和使用Java I/O库时达到事半功倍的效果。
下面我用<<Java与模式>>,<<Head First Design Pattern>>或者是网上看到的一些类图来分析:
首先是InputStream类型中的装饰模式:
InputStream有七个直接的具体子类,有四个属于FilterInputStream的具体子类,如下图所示:
上图中所有的类都叫做流处理器,这个图就叫做(InputStream类型的)流处理器图。
书中提到根据输入流的源的类型,可以将这些流类分成两种,即原始流类(Original Stream)和链接流处理器(Wrapper Stream)。
原始流处理器
原始流处理器接收一个Byte数组对象,String对象,FileDiscriptor对象或者不同类型的流源对象,根据上面的图,原始流处理器包括以下四种:
ByteArrayInputStream:为多线程的通信提供缓冲区操作功能,接收一个Byte数组作为流的源。
FileInputStream:建立一个与文件有关的输入流。接收一个File对象作为流的源。
PipedInputStream:可以与PipedOutputStream配合使用,用于读入一个数据管道的数据,接收一个PipedOutputStream作为源。
StringBufferInputStream:将一个字符串缓冲区转换为一个输入流。接收一个String对象作为流的源。(JDK帮助文档上说明:已过时。此类未能正确地将字符转换为字节。从JDK1.1开始,从字符串创建流的首选方法是通过StringReader类进行创建。只有字符串中每个字符的低八位可以由此类使用。)
链接流处理器
所谓链接流处理器,就是可以接收另一个流对象作为源,并对之进行功能扩展的类。InputStream类型的链接处理器包括以下几种,它们都接收另一个InputStream对象作为流源。
(1)FilterInputStream称为过滤输入流,它将另一个输入流作为流源。这个类的子类包括以下几种:
BufferedInputStream:用来从硬盘将数据读入到一个内存缓冲区中,并从缓冲区提供数据。
DataInputStream:提供基于多字节的读取方法,可以读取原始类型的数据。
LineNumberInputStream:提供带有行计数功能的过滤输入流。
PushbackInputStream:提供特殊的功能,可以将已经读取的字节“推回”到输入流中。
(2)ObjectInputStream可以将使用ObjectInputStream串行化的原始数据类型和对象重新并行化。
(3)SeqcueneInputStream可以将两个已有的输入流连接起来,形成一个输入流,从而将多个输入流排列构成一个输入流序列。
抽象结构图
按照上面的这种原始流处理器和链接流处理器的划分,可以用下面的结构图来描述它们之间的关系。
上面的流处理器图跟装饰模式的结构图有着显而易见的相同之处。实际上InputStream类型的流处理器结构确实符合装饰模式。
装饰模式结构图
对于上图FilterInputStream查看JDK1.6源代码,部分代码如下:
FilterInputStream继承了InputStream,也引用了InputStream,而它有四个子类,这就是所谓的Decorator模式。
上面这个图向我们传达了这个信息:链接流对象接收一个原始流对象或者另外一个链接流对象作为流源;另一方面他们对流源的内部工作方法做了相应的改变,这种改变是装饰模式所要达到的目的。比如:
BufferedInputStream“装饰”了InputStream的内部工作方式,使得流的读入操作使用了缓冲机制。在使用了缓冲机制后,不会对每一次的流读入操作都产生一个物理的读盘动作,从而提高了程序的效率,在汲及到物理流的读入时,都应当使用这个装饰流类。
LineNumberInputStream和PushbackInputStream也同样“装饰”了InputStream的内部工作方式,前者使得程序能够按照行号读入数据;后者能够使程序读入的过程中,退后一个字符。
DataInputStream子类读入各种不同的原始数据类型以及String类型的数据,这一点可以从它提供的各种read方法看出来,如:readByte(),readInt(),readFloat()等。
Java语言的I/O库提供了四大等级结构:InputStream,OutputStream,Reader,Writer四个系列的类。InputStream和OutputStream处理8位字节流数据, Reader和Writer处理16位的字符流数据。InputStream和Reader处理输入, OutputStream和Writer处理输出,所以OutputStream,Reader,Writer这三类的装饰模式跟前面详细介绍的InputStream装饰模式大同小异,大家可以看书中其它部分对这三类的详细描述或者从网上也能找到有关资料。为了方便比较这几种类型,顺便附上Java语言的I/O层次结构图:
下面的图表示:以InputStream和OutputStream形成的层次关系
下面的图表示:以Reader和Writer形成的层次关系