设计模式之装饰者模式

【情景模式】
说实话,在第一次遇到Java IO的时候,我是拒绝的,为了读写一个文件,需要一大堆代码,还有很多很多类,在不了解这些类的功能的情况下,很容器犯迷糊。之后在系统学习了IO之后明白了其实这就是装饰者模式
那么,之前一直抵制这么多类的IO,如果不用装饰者模式,应该怎么实现呢
【不用装饰者模式分析】
我们的最终目的是一个功能只需要一个类来实现,比如读取文件就只需要new BufferedFileReader()即可,不用再传FileReader()对象参数。

Java IO中,根据应用场景不同需要:
– 根据字节流和字符流分为两个基本类
– 根据输入输出源不同分为CharArray,File,String等等
– 根据功能不同需要有能缓存数据,能一次读取一行等等

因此,我们可以先实现两个基本类,分别是字符和字节,然后再根据源不同实现他们的功能类,比如CharArray,File等,最后再通过继承实现他们的辅助功能类, 比如BufferedFileOutputStream,DataOutputStream,LineNumberOutputStream等等,为每一个类都包装上功能。这样看来,
但是,这是只需要一个辅助功能的情况,按照Java IO的设计,有时候辅助功能是能叠加使用的,比如对于压缩文件来说,首先需要一个BufferedFileOutputStream作为缓存增加性能,然后需要一个ZipoutputStream来对最终的文件压缩输出。这样的组合每个可能都有,比如DataOutputStreamBufferedOutputStream等。。。
算一算排列组合,对于4个辅助功能类来说,理想组合情况大约都有十几种,这无疑来说是更大的灾难。
【代码分析】
因此,从这个角度来说,装饰者模式还算一个不错的选择,装饰者模式在这种有多种可组合选择的情况下,可以发挥出比继承更加灵活的功能。

以下代码为伪代码,和JDK代码稍微有些不同

//核心类接口

public abstract class InputStream{

    public abstract int read() throws IOException;
}

//底层功能类实现之一:输入源为byte array

public class ByteArrayInputStream extends InputStream {   


    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }
}

//底层功能类实现之一:输入源为File

public class FileInputStream extends InputStream{

    public int read() throws IOException {
        return read0();
    }

    private native int read0() throws IOException;
}

//装饰类接口

public class FilterInputStream extends InputStream {

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

    public int read() throws IOException {
        return in.read();
    }
}

//实现具有装饰功能的类之一:具有缓存功能

public class BufferedInputStream extends FilterInputStream {

   public BufferedInputStream(InputStream in, int size) {
        super(in);
        buf = new byte[size];
    }

   public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

}

//实现具有装饰功能的类之一:具有压缩功能

public class GZIPInputStream extends FilterInputStream{

    public GZIPInputStream(InputStream in) throws IOException {
        this(in, 512);
    }

   public int read(byte[] buf, int off, int len) throws IOException {
        ensureOpen();
        if (eos) {
            return -1;
        }
        int n = super.read(buf, off, len);
        if (n == -1) {
            if (readTrailer())
                eos = true;
            else
                return this.read(buf, off, len);
        } else {
            crc.update(buf, off, n);
        }
        return n;
    }

}

可以看到,上面的类中,我们实现了两个基本的类和两个装饰的类,我们可以根据实际情况来选择所需要的类,比如对于需要读取大文件来说,就需要使用FileInputstream以及BufferedInputsteam来节约性能。

同样如果我们需要读取一个压缩文件中的文件,可以同时搭配FileInputstreamBufferedInputsteam以及GZIPInputStream实现既有效率又能读取压缩文件的类。


通过以上代码就能发现,装饰模式主要是应用在具有多个功能相同的子类, 并且使用的时候可能会存在选择的情况。


装饰者模式

定义:动态的给一个对象增加一些额外的职责,对需要增加对象的功能来说,装饰者模式比生成子类更加灵活。

UML:

image

  • Component:抽象组件,最核心的功能接口
  • ConcreteComponent:具体功能的底层实现
  • Decorator:装饰者接口,通常为一个抽象类,类中一般包含一个Component元素
  • ConcreteDecoratorA:装饰者的实现类,用于实现具体的装饰者

应用场景:装饰模式一般应用在需要为现有的子类扩展功能的时候,装饰者模式是对组合优于继承的最好的诠释,我们可以通过装饰者模式,动态的扩展一个类的功能,并且能够将功能的扩展交给用户自己选择。

不过装饰者模式的确定也比较明显,那就是使用这个类的用户需要明白很多的类的作用,具有一定的学习成本,而且一般来说,最常使用的就几种组合,每次使用都会new更多的对象。

优点:

  • 装饰类和被装饰类可以独立扩展,不会相互耦合,修改被装饰的底层类,一般不会影响到装饰类,结合了继承的优点:功能复用,并且屏蔽了继承的缺点
  • 装饰类可以实现动态的扩展底层类的实现,可以通过组合实现多个功能的组合类,而继承只要编译完成,类的功能就已经确定
  • 装饰类的动态扩展的特点,可以很好的解决多个类需要配合使用的情况。

缺点

  • 多层的装饰是比较复杂的
  • 尽量减少装饰类的数量,以便降低系统的复杂度