设计模式----结构型模式之装饰者模式(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这三个类。

  1. 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设计模式》书中星巴克的例子,如果不合理设计,会产生类爆炸,从而增加项目维护的成本和困难。装饰者模式同时遵循着开放-关闭原则(对扩展开发,对修改关闭)。

  装饰者模式使用要点:

  1. 装饰者类和被装饰者类必须实现自同一个接口(抽象类)。FilterInputStream类和FileInputStream类都是继承抽象类InputStream;
  2. 装饰者内部必须包含派生类或接口的依赖,即组合。FilterInputStream类声明InputStream in;

  最后,将JDK中运用的设计模式理解,以及阅读源码。感觉受益匪浅!

  

 

posted on 2017-03-31 14:09  轻浅初夏  阅读(150)  评论(0)    收藏  举报

导航