欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式——装饰器模式

程序员文章站 2022-05-18 19:30:23
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 装饰器模式结构图 Component(抽象构件) :它是装饰类和具体构件的公共父类(一般是接口或者抽象类); ConcreteComp ......

装饰器模式(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 部分的装饰器模式开始。模仿是创新的开始。

参考

  • https://www.runoob.com/design-pattern/decorator-pattern.html
  • https://my.oschina.net/u/3178270/blog/2906791
  • https://www.cnblogs.com/volcano-liu/p/10897897.html