1 输入-输出对称性,比如InputStream和OutputStream各自占据Byte流的输入与输出的两个平行的等级结构的根部。而Reader和Writer各自占据Char流的输入与输出的两个平行的等级结构的根部。
2 byte-char对称,InputStream和Reader的子类分别负责Byte和Char流的输入;OutputStream和Writer的子类分别负责Byte和Char流的输出,它们分别形成平行的等级结构。
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代表的等级结构内部,有一些流处理器是对其它类型的流源的适配。这就是适配器模式的应用,如下图所示。
Decorator模式
为什么不用继承而用装饰模式
我们知道Java I/O库需要很多性能的各种组合,如果说这些性能的组合是通过继承方式来实现的话,那么每一种组合都需要一个类,这样就会出现大量重复性问题的出现,从而使类数目“爆炸”。而如果采用装饰模式,那么不仅类的数目大减少了,性能的重复也可以减至到最少。所以装饰模式是Java I/O库的基本模式。在这里我想再用<<Head First Design Pattern>>中讲到装饰模式时候的一个例子,看看装饰模式是怎么达到不仅类的数目大减少了,性能的重复也可以减至到最少:
它这个例子大概是说:Beverage是一个抽象类,它被所有在一个咖啡店里卖的饮料继承。Beverage有个抽象方法cost,所有的子类都要实现这个抽象方法,计算它们的价格。现在有四个最基本的咖啡:HouseBlend,DarkRoast,Decaf,Espresso他们都继承自Beverage,现在的需求是说在四个最基本的咖啡里,每个都可以随便地添加调味品,像steamed milk,soy,还有mocha最后是加上whipped milk。如果是说按继承来实现这种几个调味品跟原来咖啡的组合的话,我们会很自然地设计来下面的类图来:
看到了上面的类图了吗,我们不禁会说这就是“类爆炸”。如果是按装饰模式的设计思路我们可以得出下面的设计类图:
我们再来看看Gof里面的标准的装饰模式的类图表示:
仔细看看上面的几个图后我们肯定就会理解这句话了:装饰模式是怎么达到不仅类的数目大减少了,性能的重复也可以减至到最少。
再回到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.4源代码,部分代码如下:
上面这个图向我们传达了这个信息:链接流链接流对象接收一个原始流对象或者另外一个链接流对象作为流源;另一方面他们对流源的内部工作方法做了相应的改变,这种改变是装饰模式所要达到的目的。比如:
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形成的层次关系
Decorator模式实例
1) 抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。
2) 具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。
3) 装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。
4) 具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。
decorator的结构如下:
MyInterface
|
_______|_______
| |
Myclass Decorator
____|_____
| |
DecoratorA DecoratorB
decorator的目的是在不改变任何原有的类的基础下,添加新的功能(你可以理解为书上说的灵活性)。其中Myclass是你要扩展的类,DecoratorA跟DecoratorB封装了你要扩展的功能,并保存有一个MyInterface的引用。
考虑以下代码:
public static void main(Strings[] arg){
myInterface a = new myClass();
a.print();
}
myInterface 是myClass的接口,只声明了一个方法print(),myClass实现了该方法:
public void print(){
System.out.println("hello");
}
那么假如我们要在不改变原来的myClass的基础上,变成输出“hello world!”,要怎么做呢?
当然我们可以考虑直接写个myClass的子类,helloClass之类,但是要是要求根据环境不同,输出"hello world!",my hello world","my Hello"之类的组合呢?
用继承的方式将不得不写一堆类似的子类来。
decorator,装饰模式的解决方法是,只实现基本的功能,把附加的功能抽出来放一边。
例如以下代码:
class DecoratorA implements Decorator{
MyInterface myObject;
DecoratorA(myInterface myObject){
this.myObject = myObject;
}
public void print(){
myObject.print();
System.out.print("world!");
}
}
class DecoratorB implements Decorator{
MyInterface myObject;
DecoratorB(myInterface myObject){
this.myObject = myObject;
}
public void print(){
System.out.print("my");
myObject.print();
}
}
DecoratorA和DecoratorB的功能分别是打印出world跟my。这时main函数要打印出my hello world可简单变为:
public static void main(Strings[] arg){
MyInterface a =new DecoratorA(new DecoratorB(new MyClass());
a.print();
}