侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 85 篇文章
  • 累计创建 40 个标签
  • 累计收到 0 条评论

再谈装饰器模式

qingtian
2022-04-16 / 0 评论 / 0 点赞 / 43 阅读 / 4,024 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-16,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

装饰器模式

Decorator Pattern 装饰器模式的代码结构与桥接模式十分相似,但是解决的问题却不同。

从Java IO类中理解装饰器模式


下面是Java的IO类的一个应用实例:

InputStream in = new FileInputStream("/user/guank/test.txt");
        InputStream bin = new BufferedInputStream(in);
        byte[] data = new byte[128];
        while (bin.read(data) != -1) {
            //.....
        }

基于继承的设计方案

如果 InputStream 只有一个子类 FileInputStream 的话,那我们在 FileInputStream 基础之上,再设计一个孙子类 BufferedFileInputStream,也算是可以接受的,毕竟继承结构还算简单。但实际上,继承 InputStream 的子类有很多。我们需要给每一个 InputStream的子类,再继续派生支持缓存读取的子类。

除了支持缓存读取之外,如果我们还需要对功能进行其他方面的增强,比如下面的DataInputStream 类,支持按照基本数据类型(int、boolean、long 等)来读取数据。

InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(in);
int data = din.readInt();

在这种情况下,如果我们继续按照继承的方式来实现的话,就需要再继续派生出DataFileInputStream、DataPipedInputStream 等类。如果我们还需要既支持缓存、又支持按照基本类型读取数据的类,那就要再继续派生出 BufferedDataFileInputStream、BufferedDataPipedInputStream 等 n 多类。这还只是附加了两个增强功能,如果我们需要附加更多的增强功能,那就会导致组合爆炸,类继承结构变得无比复杂,代码既不好扩展,也不好维护。

基于装饰器模式的设计方案

public abstract class InputStream implements Closeable {
     public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    public int read(byte b[], int off, int len) throws IOException {
        Objects.checkFromIndexSize(off, len, b.length);
        if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }
    public int available() throws IOException {
        return 0;
    }
    public void close() throws IOException {}
    public synchronized void mark(int readlimit) {}
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }
    
     public boolean markSupported() {
        return false;
    }
}

-------------------------------------------------------------------------------
public
class FilterInputStream extends InputStream {
    protected volatile InputStream in;
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
}

----------------------------------------------------------------------------------
public
class BufferedInputStream extends FilterInputStream {
    
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

---------------------------------------------------------------------------------
public
class DataInputStream extends FilterInputStream implements DataInput {
    public DataInputStream(InputStream in) {
        super(in);
    }
    //...................
}

上面的代码使用了继承了同一父类,并且通过组合的方式将自己和父类组合在一起。

从 Java IO 的设计来看,装饰器模式相对于简单的组合关系,还有两个比较特殊的地

方。

  • 第一个比较特殊的地方是:装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌

    **套”多个装饰器类。**比如,下面这样一段代码,我们对 FileInputStream 嵌套了两个装饰

    器类:BufferedInputStream 和 DataInputStream,让它既支持缓存读取,又支持按照基

    本数据类型来读取数据。

    InputStream in = new FileInputStream("/user/guank/test.txt");
    InputStream bin = new BufferedInputStream(in);
    DataInputStream din = new DataInputStream(bin);
    int data = din.readInt();
    
  • 第二个比较特殊的地方是:装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重

    **要特点。**实际上,符合“组合关系”这种代码结构的设计模式有很多,比如之前讲过的代理

    模式、桥接模式,还有现在的装饰器模式。尽管它们的代码结构很相似,但是每种设计模式

    的意图是不同的。就拿比较相似的代理模式和装饰器模式来说吧,代理模式中,代理类附加

    的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功

    能。

    //代理模式的代码结构(下面的接口也可以替换成抽象类)
    public interface IA {
            void f();
        }
    public class A implements IA {
    
        @Override
        public void f() {
    
        }
    }
    public class AProxy implements IA {
        private IA a;
        public AProxy(IA a) {
            this.a = a;
        }
        @Override
        public void f() {
            //新添加的代理逻辑
            a.f();
            //新添加的代理逻辑
        }
    }
    -----------------------------------------------------------------------
    装饰器模式的代码结构
    public interface IA {
            void f();
        }
    public class A implements IA {
    
        @Override
        public void f() {
    
        }
    }
    public class ADecorator implements IA {
        private IA a;
        public ADecorator(IA a) {
            this.a = a;
        }
        @Override
        public void f() {
            //功能增强代码
            a.f();
            //功能增强代码
        }
    }
    

装饰器模式的注意点

  • 对于装饰器模式而言,即使装饰器类不需要对一些功能点进行增强,装饰器类也需要把原始类的方法重新实现一下,简单包裹对原始类的调用。具体代码如下:

    public
    class FilterInputStream extends InputStream {
        protected volatile InputStream in;
        protected FilterInputStream(InputStream in) {
            this.in = in;
        }
        //f()函数不需要增强,只是重新调用一下原始类的f()函数
        public void f() {
            in.f();
        }
    }
    
0

评论区