装饰者模式在Java 字节输入流中的应用

参考文献:
1. Head First 设计模式
2. jdk源码

在 Head First 设计模式中,装饰者模式的框架图如下

装饰者模式在Java 字节输入流中的应用_第1张图片

查看Java 源码,其中的字节输入流主要类关系如下

装饰者模式在Java 字节输入流中的应用_第2张图片

将Java的字节输入流类图与装饰者模式的框架图比对,可以知道ByteArrayInputStream、FileInputStream、ObjectInputStream、
StringBufferInputStream、SequenceInputStream、PipedInputStream为具体的组件类,对应ConcreteComponent,其均继承抽象类InputStream,对应Component,因此也都需要实现抽象方法read()。而FilterInputStream则对应Decorator,JDK源码中使用抽象类来实现FilterInputStream,可以看到类FilterInputStream中有一个类型为InputStream的成员变量in,由于InputStream为抽象类,无法直接实例化。因此成员变量in实际上是指向了抽象类InputStream的子类的,也就是需要被装饰的具体组件类。在装饰者类中,其主要还是需要具体组件类(即被装饰者类)来实现核心功能的,比如read()方法,而装饰者类提供的功能则是在具体组件类的基础上做更方便的处理。这也印证了书中:装饰者可以在被装饰者的行为前面与/或后面加上自己额行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。
我们以FileInputStream和BufferedInputStream为例来分析一下。InpuStream部分源码如下:

装饰者模式在Java 字节输入流中的应用_第3张图片

在InputStream类中,有一个抽象的read()方法,还有其他两个已经实现了的重载的read方法,其他两个重载的read方法都是通过这个抽象的read()方法实现。我们看下子类(即具体的组件类也称被装饰者类)FileInputStream是怎么实现的?

装饰者模式在Java 字节输入流中的应用_第4张图片

可以看到FileInputStream实现了read()方法,真正读取数据,其是通过本地方法read0()来实现,同时它还覆盖了父类的另外两个重载的read方法,通过本地方法readBytes实现,之所以说FileInputStream是具体组件类,因为它实现了核心功能,真正获取数据。现在看下装饰者部分,先看下所有装饰者的基类FilterInputStream,装饰者和被装饰者必须是同样的类型,因为需要装饰者能取代被装饰者,因此装饰者基类FilterInputStream继承了抽象类InputStream,同时FilterInputStream定义了一个类型为InputStream的成员变量in,用来指向被装饰者或具体组件类,成员变量in在FilterInputStream的构造函数中被赋值。部分代码如下:

装饰者模式在Java 字节输入流中的应用_第5张图片

可以看到装饰者基类FilterInputStream实现的read()方法,是通过被装饰者in来实现的,同时它也覆盖了父类InputStream的另外两个重载的read方法。我们来看一个装饰者类BufferedInputStream,BufferedInputStream是一个带缓冲的输入流,它继承了FilterInputStream,也就继承了InputStream,因此装饰者和被装饰者都用共同的基类InputStream。BufferedInputStream的构造函数如下:

装饰者模式在Java 字节输入流中的应用_第6张图片

在BufferedInpuStream中,有一个成员变量buf,为一个字节数组,默认大小为8192字节。在构造BufferedInputStream对象时,我们需要传入一个InputStream对象,这个对象即我们要装饰的被装饰者类,比如FileInpuStream等。在构造函数中,BufferedInputStream将指向被装饰者类的InputStream对象保存在父类FilterInputStream的成员变量in中,这样每个装饰者类便有一个被装饰者类的引用。装饰者类并不实现核心功能,即真正读取数据,其只在被装饰者类功能的基础上做一定的处理即装饰,在这里BufferedInputStream做的装饰即为加上了一个缓冲区,减少I/O次数。再看下BufferedInputStream实现的read()方法,如下

装饰者模式在Java 字节输入流中的应用_第7张图片

成员变量pos代表当前读取数组buf的位置,count代表数组buf包含的实际数据大小,count介于0-(buf.length-1)之间,刚开始pos和count均为0,因此会调用fill()从输入流中一次性读取buf大小的数据,缓存到数组buf中,之后读取的话便可以直接从buf里取数据而不用从流中读取了,减少I/O次数。我们看下fill()的实现

装饰者模式在Java 字节输入流中的应用_第8张图片

方法getInIfOpen()获取之前传入进来的InputStream对象in,即指向被装饰者,一开始成员变量markpos为-1,因此else if代码块不会执行,来到了int n=getInIfOpen().read(buffer,pos,buffer.length-pos),这里即利用被装饰者的功能,一次性读取buffer.length大小的数据,保存在自身的成员变量buf中。因此当我们调用read()方法时,如果buf还有数据的话,便会从buf中直接读取数据了。
我们一般使用的时候就就像这样 BufferedInputStream bfin=new BufferedInputStream(new FileInputStream(“file.txt”));
即先创建一个被装饰者对象,接着创建一个装饰者对象用来装饰。

你可能感兴趣的:(java,设计模式,装饰者模式,Java-I-O)