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

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


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

这里写图片描述

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

这里写图片描述

将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部分源码如下:

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

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

这里写图片描述

方法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”));
即先创建一个被装饰者对象,接着创建一个装饰者对象用来装饰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zlp1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值