阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Filter模式

29次阅读
没有评论

共计 4468 个字符,预计需要花费 12 分钟才能阅读完成。

Java 的 IO 标准库提供的 InputStream 根据来源可以包括:

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

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

BufferedFileInputStream extends FileInputStream {}

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

DigestFileInputStream extends FileInputStream {}

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

CipherFileInputStream extends FileInputStream {}

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

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

                          ┌─────────────────┐
                          │ FileInputStream │
                          └─────────────────┘
                                   ▲
             ┌───────────┬─────────┼─────────┬───────────┐
             │           │         │         │           │
┌───────────────────────┐│┌─────────────────┐│┌─────────────────────┐
│BufferedFileInputStream│││DigestInputStream│││CipherFileInputStream│
└───────────────────────┘│└─────────────────┘│└─────────────────────┘
                         │                   │
    ┌─────────────────────────────┐ ┌─────────────────────────────┐
    │BufferedDigestFileInputStream│ │BufferedCipherFileInputStream│
    └─────────────────────────────┘ └─────────────────────────────┘

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

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

为了解决依赖继承会导致子类数量失控的问题,JDK 首先将 InputStream 分为两大类:

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

  • FileInputStream
  • ByteArrayInputStream
  • ServletInputStream

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

  • BufferedInputStream
  • DigestInputStream
  • CipherInputStream

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

InputStream file = new FileInputStream("test.gz");

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

InputStream buffered = new BufferedInputStream(file);

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

InputStream gzip = new GZIPInputStream(buffered);

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

┌─────────────────────────┐
│GZIPInputStream          │
│┌───────────────────────┐│
││BufferedFileInputStream││
││┌─────────────────────┐││
│││   FileInputStream   │││
││└─────────────────────┘││
│└───────────────────────┘│
└─────────────────────────┘

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

                 ┌─────────────┐
                 │ InputStream │
                 └─────────────┘
                       ▲ ▲
┌────────────────────┐ │ │ ┌─────────────────┐
│  FileInputStream   │─┤ └─│FilterInputStream│
└────────────────────┘ │   └─────────────────┘
┌────────────────────┐ │     ▲ ┌───────────────────┐
│ByteArrayInputStream│─┤     ├─│BufferedInputStream│
└────────────────────┘ │     │ └───────────────────┘
┌────────────────────┐ │     │ ┌───────────────────┐
│ ServletInputStream │─┘     ├─│  DataInputStream  │
└────────────────────┘       │ └───────────────────┘
                             │ ┌───────────────────┐
                             └─│CheckedInputStream │
                               └───────────────────┘

类似的,OutputStream也是以这种模式来提供各种功能:

                  ┌─────────────┐
                  │OutputStream │
                  └─────────────┘
                        ▲ ▲
┌─────────────────────┐ │ │ ┌──────────────────┐
│  FileOutputStream   │─┤ └─│FilterOutputStream│
└─────────────────────┘ │   └──────────────────┘
┌─────────────────────┐ │     ▲ ┌────────────────────┐
│ByteArrayOutputStream│─┤     ├─│BufferedOutputStream│
└─────────────────────┘ │     │ └────────────────────┘
┌─────────────────────┐ │     │ ┌────────────────────┐
│ ServletOutputStream │─┘     ├─│  DataOutputStream  │
└─────────────────────┘       │ └────────────────────┘
                              │ ┌────────────────────┐
                              └─│CheckedOutputStream │
                                └────────────────────┘

编写 FilterInputStream

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

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

import java.io.*;

public class Main {public static void main(String[] args) throws IOException {byte[] data = "hello, world!".getBytes("UTF-8");
        try (CountInputStream input = new CountInputStream(new ByteArrayInputStream(data))) {int n;
            while ((n = input.read()) != -1) {System.out.println((char)n);
            }
            System.out.println("Total read" + input.getBytesRead() + "bytes");
        }
    }
}

class CountInputStream extends FilterInputStream {private int count = 0;

    CountInputStream(InputStream in) {super(in);
    }

    public int getBytesRead() {return this.count;
    }

    public int read() throws IOException {int n = in.read();
        if (n != -1) {this.count ++;
        }
        return n;
    }

    public int read(byte[] b, int off, int len) throws IOException {int n = in.read(b, off, len);
        if (n != -1) {this.count += n;
        }
        return n;
    }
}

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

小结

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

  • 可以把一个 InputStream 和任意个 FilterInputStream 组合;
  • 可以把一个 OutputStream 和任意个 FilterOutputStream 组合。

Filter 模式可以在运行期动态增加功能(又称 Decorator 模式)。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计4468字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中