zoukankan      html  css  js  c++  java
  • 设计模式——装饰器模式

    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

    装饰器模式结构图

    结构图

    • Component(抽象构件):它是装饰类和具体构件的公共父类(一般是接口或者抽象类);
    • ConcreteComponent(具体构件):具它是抽象构件对象的子类,用来定义具体的构件对象(Component的子类或者具体实现);
    • Decorator(抽象装饰类):进继承抽象构件,用于给具体的构件添加一些新的职责(一般是一个继承了Component的接口);
    • ConcreteDecoraror(具体装饰类):实现了抽象装饰类,它负责向构件添加新的职责;

    代码演示

    业务场景:现我们现在模拟这样一个场景,我们点了一杯奶茶,然后给奶茶中加了冰块,加了珍珠,最后我们还想再给加点红豆,这里加红豆就使用了装饰者。

    我们先来创建一个奶茶的抽象类,这个就是上面的Component角色

    public interface MilkyTea {
    
       public void recipe();
    }
    

    我们再来创建要给奶茶的具体子类,相当于ConcreteComponent

    public class MilkyTeaA implements MilkyTea {
       @Override
       public void recipe() {
           System.out.println("老板来一杯奶茶,加冰块");
       }
    }
    

    接下来创建一个装饰类,相当于Decorator

    public class Decorator implements MilkyTea {
    
       private MilkyTea milkyTea;
    
       public void setMilkyTea(MilkyTea milkyTea) {
    
           this.milkyTea = milkyTea;
       }
    
       @Override
       public void recipe() {
           milkyTea.recipe();
       }
    }
    

    创建装饰类的子类,添加珍珠,相当于ConcreteDecorator

    public class MilkyTeaADecorator extends Decorator {
    
       @Override
       public void recipe() {
           super.recipe();
           //对现有类进行功能增强
           recipeZZ();
       }
    
       // 加珍珠
       public void recipeZZ() {
           System.out.println("老板再加点珍珠吧");
       }
    }
    

    创建装饰者的子类,添加红豆,相当于ConcreteDecorator

    public class MilkyTeaBDecorator extends Decorator {
    
       @Override
       public void recipe() {
    
           super.recipe();
           recipeHD();
       }
    
       public void recipeHD() {
    
           System.out.println("老板你再给加点红豆吧");
       }
    }
    

    最后我们测试一下看下结果:

    public class Test {
    
       public static void main(String[] args) {
    
           MilkyTeaA milkyTea = new MilkyTeaA();
    
           MilkyTeaADecorator milkyTeaA = new MilkyTeaADecorator();
           MilkyTeaBDecorator milkyTeaB = new MilkyTeaBDecorator();
    
           milkyTeaA.setMilkyTea(milkyTea);
           milkyTeaB.setMilkyTea(milkyTeaA);
    
           milkyTeaB.recipe();
       }
    }
    

    JDK中的装饰器模式

    JDK中,IO部分的很多类用到了装饰器模式。

    InputStream作为抽象构件Component),其下面大约有如下几种具体基础构件ConcreteComponent),从不同的数据源产生输入:

    • ByteArrayInputStream,从字节数组产生输入;
    • FileInputStream,从文件产生输入;
    • StringBufferInputStream,从String对象产生输入;
    • PipedInputStream,从管道产生输入;
    • SequenceInputStream,可将其他流收集合并到一个流内;

    FilterInputStream作为装饰器JDK中是一个普通类,其下面有多个具体装饰器比如BufferedInputStreamDataInputStream等。我们以BufferedInputStream为例,使用它就是避免每次读取时都进行实际的写操作,起着缓冲作用。我们可以在这里稍微深入一下,站在源码的角度来管中窥豹。

    FilterInputStream内部封装了基础构件:

    protected volatile InputStream in;
    

    BufferedInputStream在调用其read()读取数据时会委托基础构件来进行更底层的操作,而它自己所起的装饰作用就是缓冲,在源码中可以很清楚的看到这一切:

    public synchronized int read() throws IOException {
            if (pos >= count) {
                fill();
                if (pos >= count)
                    return -1;
            }
            return getBufIfOpen()[pos++] & 0xff;
        }
    
    
        private void fill() throws IOException {
            byte[] buffer = getBufIfOpen();
            if (markpos < 0)
                pos = 0;            /* no mark: throw away the buffer */
            else if (pos >= buffer.length)  /* no room left in buffer */
                if (markpos > 0) {  /* can throw away early part of the buffer */
                    int sz = pos - markpos;
                    System.arraycopy(buffer, markpos, buffer, 0, sz);
                    pos = sz;
                    markpos = 0;
                } else if (buffer.length >= marklimit) {
                    markpos = -1;   /* buffer got too big, invalidate mark */
                    pos = 0;        /* drop buffer contents */
                } else if (buffer.length >= MAX_BUFFER_SIZE) {
                    throw new OutOfMemoryError("Required array size too large");
                } else {            /* grow buffer */
                    int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
                            pos * 2 : MAX_BUFFER_SIZE;
                    if (nsz > marklimit)
                        nsz = marklimit;
                    byte nbuf[] = new byte[nsz];
                    System.arraycopy(buffer, 0, nbuf, 0, pos);
                    if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
                        throw new IOException("Stream closed");
                    }
                    buffer = nbuf;
                }
            count = pos;
            // 看这行就行了,委托基础构件来进行更底层的操作
            int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
            if (n > 0)
                count = n + pos;
        }
    
        private InputStream getInIfOpen() throws IOException {
            InputStream input = in;
            if (input == null)
                throw new IOException("Stream closed");
            return input;
        }
    

    这部分的代码很多,这里我们没有必要考虑这段代码的具体逻辑,只需要看到在BufferedInputStream的read方法中通过getInIfOpen()获取基础构件从而委托其进行更底层的操作(在这里是读取单个字节)就可以说明本文所要说的一切了。

    至于I/O类库中的其他设计诸如OutputStream、Writer、Reader,是一致的,这里就不再赘述了。

    简单总结

    • 装饰类和被装饰类可以独立发展,而不会相互耦合。换句话说,Component类无需知道Decorator类,Decorator类是从外部来扩展Component类的功能,而Decorator也不用知道具体的构件。
    • 装饰器模式是继承关系的一个替代方案。我们看装饰类Decorator,不管装饰多少层,返回的对象还是Component(因为Decorator本身就是继承自Component的),实现的还是is-a的关系。
    • 装饰模式可以动态地扩展一个实现类的功能,比如在I/O系统中,我们直接给BufferedInputStream的构造器直接传一个InputStream就可以轻松构件一个带缓冲的输入流,如果需要扩展,我们继续“装饰”即可。

      但是也有其自身的缺点:

      多层的装饰是比较复杂的。为什么会复杂?你想想看,就像剥洋葱一样,你剥到最后才发现是最里层的装饰出现了问题,可以想象一下工作量。这点从我使用Java I/O的类库就深有感受,我只需要单一结果的流,结果却往往需要创建多个对象,一层套一层,对于初学者来说容易让人迷惑。

    理论的学习还是为了实践。实战中如果需要用到装饰器模式,可以从模仿 Java IO 部分的装饰器模式开始。模仿是创新的开始。

    参考

  • 相关阅读:
    C#基元类型、引用类型和值类型
    UML类图中泛化、实现、依赖、关联、聚合、组合关系
    简述:聚集索引和非聚集索引的区别
    面向对象编程的三特性、七原则和六视点
    设计模式学习笔记——解释器模式(Interpreter)
    设计模式学习笔记——组合模式(Composite)
    程序员编程利器:20款最好的免费的IDEs和编辑器
    奇技淫巧之浏览器秒秒钟变编辑器
    前端技术Jquery与Ajax使用总结
    Chrome也疯狂之Vimium插件
  • 原文地址:https://www.cnblogs.com/54chensongxia/p/12467971.html
Copyright © 2011-2022 走看看