装饰器模式

装饰器模式

为什么设计装饰器模式

  • 核心:[装饰器模式是继承关系的一种替代方案:可以动态地扩展实现类的功能]{.red}

  • 情景:

    • 经常游玩 FPS 类型游戏的玩家都知道枪械是会有各种配件的

    • 如果不为枪械安装任何配件那么就只是一把最普通的枪,如果需要具有更多功能的枪械显然就需要安装更多的配件

    • 现在将枪械和配件的关系抽象出来,最直接的解决方案就是继承:每个具有不同配件的枪械都是普通枪的子类,对普通枪进行扩展

      6a1b1256badfadcf7b03d4ae0cb5fc29.png
    • 继承关系可以在枪械和配件之间建立联系,同时也带来了相应的问题:

      (1) [子类数量会随着需求大量增长]{.red}:存在非常多的配件那么子类的数量就会爆炸

      (2) [难以满足复合的需求]{.red}:有些枪械既需要瞄准镜又需要垂直握把,显然继承关系就难以满足这种复合的需求

    • 重新将枪械和配件的关系抽象出来,采用装饰器模式:将枪械的各个配件和普通的枪进行分离,需要什么配件就对普通的枪械进行装饰

      1452502dbb05482503a68f7f1bb49d52.png
    • 装饰器模式显然可以避免继承关系带来的子类数量爆炸的问题并且满足复合需求:[只需要利用组合关系就可以完成装饰器模式]{.red}

什么是装饰器模式

  • 定义:[动态地将责任分配到附加到对象上,如果需要扩展基类的功能,只需要利用组合就可以完成扩展]{.red}

  • 细节:[每个基本类被多个装饰类修饰之后仍然是抽象类的实例]{.red}

  • 优先 & 缺点

    • 优点:[利用组合关系替代了继承关系显著降低了类与类之间的耦合度]{.red}:每个基本类和装饰类都是相互独立的,只在有需要的时候存在联系
    • 缺点:[装饰类过多时容易造成代码阅读困难和排错困难]{.red}:装饰器模式创建对象通常采用嵌套的方式创建,所以看着会比较迷惑
  • 类图:

    74690f6c1f6157a138932a9907105d79.png
  • 实现:

    • Component 抽象类:提供该类型的类需要满足的最基本功能

      /*对应枪械例子中的枪模*/
      public abstract class Component{
      public abstract void fire(); // 枪械开火
      public abstract void reload(); // 枪械装弹
      }
    • ConcreteComponent:具有基本功能的实现类([可以提供多个不同实现的基本类]{.red})

      /*对应枪械例子中的普通枪械*/
      public class ConcreteComponent extends Component{
      @Override
      public void fire(){
      System.out.println("开火..."); // 实现枪械最基本的功能:开火
      }
      @Override
      public void reload(){ // 实现枪械最基本的功能:装弹
      System.out.println("装弹...");
      }
      }
    • Decorator:装饰类通常 只是调用基本类 的方法并不会做进一步的扩展

      /*对应枪械例子中的配件类*/
      public class Decoration extends Component{
      private Component component = null;
      public Decoration(Component component){ // 利用组合获取到基本类
      this.component = component;
      }
      @Override
      public void fire(){ // 实现枪械最基本的功能但是不做任何扩展,只是调用基本类的方法
      component.fire();
      }
      @Override
      public void reload(){
      component.reload();
      }
      }
    • ConcreteDecoretor:可以拓展功能的具体装饰类

      public class ConcreteDecorationB extends Decoration{
      public ConcreteDecorationB(Component component){
      super(component);
      }
      @Override
      public void fire(){
      System.out.println("装备垂直握把..."); // 拓展的逻辑功能
      super.fire();
      }
      @Override
      public void reload(){
      System.out.println("装备垂直握把...");
      super.reload();
      }
      }
    • 创建具有装饰器的基本类

      public static void main(String[] args)
      {
      /*装饰器模式创建类通常都采用这种嵌套的写法:根据每个装饰器类的构造方法可以看出,每个装饰器类可以接着嵌套装饰器*/
      Component component = new ConcreteDecorationA( // 装备瞄准镜
      new ConcreteDecorationB( // 装备垂直握把
      new ConcreteDecorationC( // 装备消音器
      new ConcreteComponent()))); // 最基本的类在最里面
      component.fire();
      }

应用装饰器模式

  • 核心应用:[Java I/O 框架完全采用装饰器模式设计]{.red}

  • 情景:

    • 每个输入源存放的数据类型可能各不相同:有些输入源提供的是字节流(ByteArrayInputStream),有些输入源提供的是文件(FileInputStream),这些都是基本类
    • 每个输入源在传输数据的过程中的要求也不尽相同:有些输入源可能要求提供缓冲区(BufferedInputStream),有些输入源可能要求提供类型转换的功能(DataInputStream),显然这些类就是 装饰类
    • 只需要将基本类和装饰类进行组合就可以得到满足要求的输入环境
  • 常用基本类:ByteArrayInputStreamFileInputStreamPipeInputStreamSequenceInputStream

  • 常用装饰类:DataInputStreamBufferedInputStreamPushBackInputStream

    /* FilteInputStream 父装饰类具有成员变量 */
    protected volatile InputStream in;
    /*构造方法:只需要传入进程了该抽象类的基本类或者装饰类即可*/
    protected FilterInputStream(InputStream in) {
    this.in = in;
    }
    /* FilteInputStream 装饰类没有对原有读取进行任何扩展*/
    public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
    }

    /* BufferedStream 装饰类扩展:具有缓冲区*/
    public synchronized int read() throws IOException {
    if (pos >= count) {
    fill();
    if (pos >= count)
    return -1;
    }
    return getBufIfOpen()[pos++] & 0xff;
    }
  • 创建被装饰器修饰的基本类对象

    /*创建被装饰的基本类对象:被装饰的文件传输类就具有了缓冲区和转换数据类型的功能*/
    InputStream in = new BufferedInputStream(
    new DataInputStream(
    new FileInputStream("")
    )
    );

参考博客:什么是装饰器模式?

Author: Fuyusakaiori
Link: http://example.com/2021/08/20/design/装饰器模式/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.