那些你学了又忘的Java IO(六):设计模式

这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

人生苦短,不如养狗

作者:Brucebat.Sun

一、前言

  在前面的章节中我们了解到了Java IO类库的基本概念和部分编程范式,对于基本使用而言,掌握这些基本内容就已经足够了。但是为了更优雅的去使用IO类库甚至是自定义实现IO类库中的接口,我们还需要更进一步的去了解IO类库中使用到设计模式。

二、IO中的设计模式

  总的来看,Java IO类库在进行编码设计时使用两种设计模式:装饰者模式适配器模式。这两种模式均属于结构型模式,也就是说IO类库在进行设计时将关注点放在类和对象的组合上。下面我们就来分别分析一下这两种设计模式在IO类库中的使用。

装饰者模式

  使用装饰者模式最大的目的就是在给一个已经存在的对象添加新的功能时,不需要改变现有的数据结构,也即不会出现类的数量爆炸的情况。和一般装饰者模式的使用略有不同的是,IO类库是通过使用一个中间类FilterInputStream及其子类来实现对于装饰者模式的使用。下面我们通过IO类库中的FilterInputStream及其子类来看一下装饰者模式的使用情况。

io装饰者模式.png

  上图是在IO类库中FilterInputStream及其子类,让我们结合装饰者模式的基本概念及其编程范式先来看一下FilterInputStream

public
class FilterInputStream extends InputStream {
    /**
     * The input stream to be filtered.
     */
    protected volatile InputStream in;
​
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
  
    ... 
}
复制代码

  将其他基本方法刨除之后可以看到,在FilterInputStream当中引入了一个由外部传入的输入流对象,而这个对象才是FilterInputStream及其子类实际操作的数据源。

  从代码不难看出,这里遵循了设计模式的两个原则。

  • 其一,里氏代换原则,也即面向抽象编程。这里引入的对象类并不是某个明确具体的类,而是所有输入流的父类InputStream,这也意味着其所有子类均可作为FilterStream及其子类的数据源。
  • 其二,合成复用原则,也即使用组合方式来替代继承。这里使用外部引入的输入流对象作为实际的数据源,在进行对应数据源读取之前,FilterInputSrream及其子类可以进行额外的数据操作逻辑(是不是感觉有点像代理模式,需要注意这里的逻辑实现是在结构上,而代理模式则是针对对象而言),这里可以简单参考下面的demo案例:
public synchronized int read() throws IOException {
  // 进行前置处理
  int data = super.read();
  // 进行后置处理
  return data;
}
复制代码

  相信讲到这里,大家应该能够通过继承FilterInputStream来实现一个能够对原生的输入流进行一些功能增强的自定义输入流。

适配器模式

  适配器模式存在的意义是为了解决两个不兼容接口或者是输出目标对象不兼容的兼容性问题。在实际的IO操作中,我们可能会遇到原始数据为字节流,但是却需要使用到字符流相关的API,这里IO类库为我们提供一个现成的适配器工具InputStreamReader。这里我们看一下InputStreamReader的构造函数:

public InputStreamReader(InputStream in) {
        super(in);
        try {
            sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
        } catch (UnsupportedEncodingException e) {
            // The default encoding should always be available
            throw new Error(e);
        }
    }
​
    public InputStreamReader(InputStream in, String charsetName)
        throws UnsupportedEncodingException
    {
        super(in);
        if (charsetName == null)
            throw new NullPointerException("charsetName");
        sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
    }
复制代码

  从上面的两个构造函数我们可以看出,这里实际上是将输入的字节流按照默认的编码集或者指定的编码集进行解码操作,在进行实际读取操作的时候将字节数据转换成字符数据。这部分内容的实现全部放置在了StreamDecoder当中,这里就不展示具体的代码,有兴趣的同学可以自己阅读一下。在阅读过代码之后会发现,在InputStreamReader当中真正进行数据读取操作的实际上是内部引入的StreamDecode对象。

三、总结

  至此,关于Java IO的基本内容基本回顾完毕,在这个系列当中笔者没有按照IO类中的类进行逐个讲解,更多的是按照过去未曾细究或者工作实际中遇到的一些点进行分享。后续如果发现其他的知识点会继续在该系列中进行补充。希望通过这个系列能够让大家有所增益。

  最后,希望疫情早日过去,世界和平,祝诸君身体健康~~

猜你喜欢

转载自juejin.im/post/7069407356387328030