Filter模式


  • FileInputStream:从文件读取数据,是最终数据源;
  • ServletInputStream:从HTTP请求读取数据,是最终数据源;
  • Socket.getInputStream():从TCP连接读取数据,是最终数据源;

如果我们要给FileInputStream添加缓冲功能,则可以从FileInputStream派生一个类:

如果要给FileInputStream添加计算签名的功能,类似的,也可以从FileInputStream派生一个类:

  1. DigestFileInputStream extends FileInputStream

如果要给FileInputStream添加加密/解密功能,还是可以从FileInputStream派生一个类:

  1. CipherFileInputStream extends FileInputStream

如果要给FileInputStream添加缓冲和签名的功能,那么我们还需要派生BufferedDigestFileInputStream。如果要给FileInputStream添加缓冲和加解密的功能,则需要派生BufferedCipherFileInputStream

我们发现,给FileInputStream添加3种功能,至少需要3个子类。这3种功能的组合,又需要更多的子类:

这还只是针对FileInputStream设计,如果针对另一种InputStream设计,很快会出现子类爆炸的情况。

因此,直接使用继承,为各种InputStream附加更多的功能,根本无法控制代码的复杂度,很快就会失控。

一类是直接提供数据的基础InputStream,例如:

  • FileInputStream
  • ByteArrayInputStream
  • ServletInputStream

一类是提供额外附加功能的InputStream,例如:

  • BufferedInputStream
  • CipherInputStream

当我们需要给一个“基础”InputStream附加各种功能时,我们先确定这个能提供数据源的InputStream,因为我们需要的数据总得来自某个地方,例如,FileInputStream,数据来源自文件:

紧接着,我们希望FileInputStream能提供缓冲的功能来提高读取的效率,因此我们用BufferedInputStream包装这个InputStream,得到的包装类型是BufferedInputStream,但它仍然被视为一个InputStream

  1. InputStream buffered = new BufferedInputStream(file);

最后,假设该文件已经用gzip压缩了,我们希望直接读取解压缩的内容,就可以再包装一个GZIPInputStream

无论我们包装多少次,得到的对象始终是InputStream,我们直接用InputStream来引用它,就可以正常读取:

  1. ┌─────────────────────────┐
  2. GZIPInputStream
  3. │┌───────────────────────┐│
  4. ││BufferedFileInputStream││
  5. ││┌─────────────────────┐││
  6. │││ FileInputStream │││
  7. ││└─────────────────────┘││
  8. │└───────────────────────┘│
  9. └─────────────────────────┘

上述这种通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(或者装饰器模式:Decorator)。它可以让我们通过少量的类来实现各种功能的组合:

  1. ┌─────────────┐
  2. └─────────────┘
  3. ┌────────────────────┐ ┌─────────────────┐
  4. FileInputStream │─┤ └─│FilterInputStream
  5. └────────────────────┘ └─────────────────┘
  6. ByteArrayInputStream│─┤ ├─│BufferedInputStream
  7. └────────────────────┘ └───────────────────┘
  8. ┌────────────────────┐ ┌───────────────────┐
  9. ServletInputStream │─┘ ├─│ DataInputStream
  10. └────────────────────┘ └───────────────────┘
  11. ┌───────────────────┐
  12. └─│CheckedInputStream
  13. └───────────────────┘

我们也可以自己编写FilterInputStream,以便可以把自己的FilterInputStream“叠加”到任何一个InputStream中。

下面的例子演示了如何编写一个CountInputStream,它的作用是对输入的字节进行计数:

注意到在叠加多个FilterInputStream,我们只需要持有最外层的InputStream,并且,当最外层的InputStream关闭时(在try(resource)块的结束处自动关闭),内层的InputStreamclose()方法也会被自动调用,并最终调用到最核心的“基础”InputStream,因此不存在资源泄露。

Java的IO标准库使用Filter模式为InputStreamOutputStream增加功能:

  • 可以把一个OutputStream和任意个组合。

Filter模式 - 图2