设计模式----结构型模式之装饰者模式(Decorator pattern)
- 走进装饰者模式
java.io软件包内封装对文件读写操作,按功能分为两大类:输入流和输出流(我的理解:按类型也分为两大类:字节和字符)。凡是继承java.io.InputStream的派生类都是实现字节输入流,继承java.io.Reader的派生类都是实现字符输入流;继承java.io.OutputStream的派生类都是实现字节输出流,继承java.io.Writer的派生类都是实现字符输出流。所谓输入,主要是程序内部实现读取外部文件信息,输出是从程序内部写信息到外部文件中。
在程序中,我们往往使用到
InputStream s = new BufferedInputStream(new FileInputStream("test.txt")); int a; while(-1 != (a = s.read())){ System.out.println(a); } s.close();
不难可以看出,上述代码主要实现从文件test.txt获取字节信息,InputStream s = new BufferedInputStream(new FileInputStream("test.txt"));改行代码特别费解,让我们一起探讨一下InputStream,BufferedInputStream和FileInputStream这三个类。
- InputStream
该类为抽象类,该类的内部方法主要包括以下几组:
(1) public abstract int read() throws IOException; //从输入流中读取数据的下一字节 (2) public int read(byte b[]) throws IOException{}; //从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中 (3) public int read(byte b[], int off, int len) throws IOException{};从输入流中读取len-off个数据字节到byte数组 (4) public synchronized void mark(int readlimit){};//标识输入流中当前位置 (5) public synchronized void reset() throws IOException{};//将此流重新定位到最后一个对此输入流调用mark方法时的位置 (6) public boolean markSupported() {};//测试此输入流是否支持mark和reset方法
2.BufferedInputStream
该类主要是为输入流添加一些特有的功能,即缓冲输入,顾名思义,对于输入流来说,主要是执行获取文件信息(new File(path))---->从输入流中读取数据的下一个字节(read()方法)---->是否读取完毕(-1 == read())---->关闭流(close()方法)。如果增加缓冲输入功能,能够减少访问磁盘的次数,提高文件读取性能。那么我们再来到BufferedInputStream类,我们发现该类是继承java.io.FilterInputStream类,如下所示:
public class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } public int read() throws IOException { return in.read(); } public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public long skip(long n) throws IOException { return in.skip(n); } public int available() throws IOException { return in.available(); } public synchronized void mark(int readlimit) { in.mark(readlimit); } public synchronized void reset() throws IOException { in.reset(); } public boolean markSupported() { return in.markSupported(); } }
FilterInputStream我们回到最开始的语句:InputStream s = new BufferedInputStream(new FileInputStream("test.txt"));使用BufferedInputStream构造方法注入InputStream派生类FileInputStream对象,BufferedInputStream构造方法也是调用自身基类FilterInputStream的构造方法实现注入,这个FileInputStream对象成为FilterInputStream类内部成员变量,s实现的输入流操作也是通过该变量来调用FileInputStream的方法来实现。
最后我们通过类层次结构来解读read()方法的使用:

s.read()执行主要分为几个步骤:
(1)调用BufferedInputStream的read()方法:
a、若缓存区【字节数组byte[] buf,下同】中的当前位置大于或等于比缓存区中最有一个有效字节的索引大1的索引,调用方法(2);
b、若缓存区中的当前位置等于比缓存区中最有一个有效字节的索引大1的索引,直接返回缓存buf的下一个字节对应的字节值;
(2)获取当前的缓存区buf:
a、若输入流中没有被标识的位置,设置缓冲区当前位置为0,执行步骤(3);
b、若输入流中已被标识,分一下四种情况(红色部分摘抄至网上,组织语言太麻烦了):
。普通mark,直接将标记以前的字符用标记以后的字符覆盖,剩余的空间读取输入流的内容填充;
。当前位置pos >= buffer的长度 >= marklimit,说明mark已经失效,直接清空缓冲区,然后读取输入流内容;
。buffer长度超出限制,抛出异常;
。marklimit比buffer的长度还大,此时mark还没失效,则扩大buffer空间;
最后执行步骤(3);
(3)从输入流中读取指定长度的字节到缓存区中(首次缓冲区缓存字节长度默认为8192),同时标识缓冲区有效长度。
至此,我们已经基本了解了字节输入流使用缓存输入流功能。那么,我们已经掌握了一个装饰者模式的jdk实例,从类层次整体框架来看,FilterInputStream充当着修饰者,BufferedInputStream充当具体修饰者,FileInputStream为被修饰者。我们直接使用BufferedInputStream的read()方法,就间接地向FilterInputStream注入读取或文件的字节输入流,使用了FileInputStream的read()方法。我们发现FilterInputStream下有许多具体修饰者,这些具体修饰者和FilterInputStream类似,都是为字节输入流实现一些辅助功能。
这个模式广泛用到java io流中,其余字节输出流和字符输入输出流也是如此,利用修饰者模式进行了很灵活的扩展。
- 装饰者模式
为什么使用装饰者模式?这是我们需要考虑的,每一个模式设计是为了解决特定问题而存在的,我在网上看到过有这么一段话:开发过程中,需要谨慎使用设计模式,设计模式的不正确使用也会导致不良效果,例如,设计模式增加他人理解代码的困难,同时为了一个简单的逻辑增加模式,也增加了项目成本和进度。只有在开发过程中,合理使用设计模式
进行重构,这种重构不会影响原来的代码逻辑,也使得源码变得更加优雅。
装饰者模式动态地往一个类中添加新的行为,这能避免修改原来的类结构。正如《head first设计模式》书中星巴克的例子,如果不合理设计,会产生类爆炸,从而增加项目维护的成本和困难。装饰者模式同时遵循着开放-关闭原则(对扩展开发,对修改关闭)。
装饰者模式使用要点:
- 装饰者类和被装饰者类必须实现自同一个接口(抽象类)。FilterInputStream类和FileInputStream类都是继承抽象类InputStream;
- 装饰者内部必须包含派生类或接口的依赖,即组合。FilterInputStream类声明InputStream in;
最后,将JDK中运用的设计模式理解,以及阅读源码。感觉受益匪浅!
浙公网安备 33010602011771号