设计模式之装饰者模式

《Head First 设计模式》阅读笔记

Posted by wangtiegang on September 1, 2019

学完装饰者模式之后发现代码实现很简单,但是有种不知道在什么场合适合应用的感觉。对于书上写的咖啡例子感觉有点强行使用,不过对于原理讲解来说很合适,暂且记录下来,后续还需要结合实际应用例子加深理解。

装饰者模式 动态的将责任附加到对象身上,若要扩展功能,装饰者模式提供了比继承更有弹性的替代方案。

通俗点讲就是在运行时将功能扩展到原来的对象上,但是不需要修改原对象的代码。这样的好处就是在设计时想不到的功能,或者在设计时没法预料的变化,可以在将来发生时再去扩展,同时由于只用扩展后的对象替换原来的对象(扩展后的对象跟原来对象是同一种类型),不直接修改原来的其他实现,降低了出bug的风险。听起来好像很厉害,但实际上要获得这样的灵活性,必须满足一些条件,这个后面再说。

在装饰者模式中,作者引出了一条设计原则

开放-关闭原则 类应该对扩展开放,对修改关闭。

这个设计原则的目标是设计的类容易扩展(开放),在不修改现有的代码的情况下(关闭),就可以搭配新的行为,达到新的功能。这样的设计具有弹性,可以更好的应对改变。

装饰者模式从一开始就设计容易扩展的类,在不修改代码的情况下搭配装饰者,在装饰者中加入新的行为,达到新的功能。

接下来我们用书上的例子来具体解释装饰者模式。

假设你开了一家咖啡店,提供美式咖啡,焦糖玛奇朵,摩卡等等种类。顾客点其中的一种,其实都是普通咖啡加各种料做成的,算价钱的时候就是把咖啡和各种料的价格加起来。如果咖啡和料都是一个个对象,那实现这个过程可能就变成了下面的样子(不太恰当。。)

1
2
3
4
5
6
public class Mocha{
    public double cost(){
        //     咖啡     +       料1     +           料2
        return coffee.cost() + condiment1.cost() + condiment2.cost(); 
    }
}

这样的实现针对具体实现编程,同时应该使用组合来实现,违背了设计原则。一旦配料发生改变,则需要修改具体的 Mocha 实现类。

如果使用装饰者模式来设计,则不存在这样的问题。

  • 设计一个饮料的超类,所有咖啡都继承这个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      /**
      * 饮料抽象类,所有饮料的超类型
      */
      public abstract class Beverage {
    
          /**
          * @return 描述
          */
          public String getDescription(){
              return "Unknown Beverage";
          }
    
          /**
          * @return 价格
          */
          public abstract double cost();
    
      }
    
  • 再设计一个装饰者,调料都继承这个类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    /**
     * 所有调料装饰者的超类型
     */
    public abstract class CondimentDecorator extends Beverage {
    
      /**
       * 所有调料都必须重新实现描述方法
       * @return 调料描述
       */
      public abstract String getDescription();
    
    }
    
  • 实现基础的咖啡

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    /**
     * 综合咖啡,饮料的子类
     */
    public class Espresso extends Beverage {
    
      @Override
      public String getDescription() {
          return "咖啡";
      }
    
      @Override
      public double cost() {
          return 1.99;
      }
    
    }
    
  • 实现各种调料

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    
    /**
     * 摩卡装饰者
     */
    public class Mocha extends CondimentDecorator {
    
      /**
       * 被摩卡封装的对象
       */
      private Beverage beverage;
    
      public Mocha(Beverage beverage){
          this.beverage = beverage;
      }
    
      @Override
      public String getDescription() {
          return "摩卡" + beverage.getDescription();
      }
    
      @Override
      public double cost() {
          return 0.55 + beverage.cost();
      }
    
    }
    
    /**
    * 奶盖装饰者
    */
    public class Whip extends CondimentDecorator {
    
      private Beverage beverage;
    
      public Whip(Beverage beverage){
          this.beverage = beverage;
      }
    
      @Override
      public String getDescription() {
          return "奶盖" + beverage.getDescription();
      }
    
      @Override
      public double cost() {
          return 0.3 + beverage.cost();
      }
    
    }
    
    
    • 现在来制作一杯奶盖摩卡咖啡
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      /**
       * 制作奶盖摩卡咖啡,计算价格
       * @param args
       */
      public static void main(String[] args) {
          // 1.普通咖啡
          Beverage espresso = new Espresso();
          // 2.加料编程摩卡咖啡
          Beverage mocha = new Mocha(espresso);
          // 3.加奶盖
          mocha = new Whip(mocha);
    
          System.out.println(mocha.getDescription() + " : " + mocha.cost() + " 元");
    
      }
    

如果需要不同类型的咖啡,只需要进行各种组合搭配就好了,这样方便进行扩展和改变。这个就是装饰者模式的具体实现了,可以看到装饰者模式就是定义一个个装饰者,跟被装饰者的类型一致,对被装饰者进行包装,然后在被装饰者的方法基础上增加功能,装饰者可以被无数次包装。

装饰者虽然为设计注入了弹性,但是也有一些缺点和要求

  • 增加了设计的复杂度,会导致有大量的小类,让使用者一下不好理解
  • 增加了实现的复杂度,在使用对应的类时,可能加很多层装饰者进行包装
  • 在别人的代码上插入装饰者模式时,必须确保别人的代码没有针对具体实现类型的编码,比如 a instanceof A.class ,如果有,则装饰者替换原A类型后,可能会导致bug。

书上介绍可以结合工厂模式和生成器模式可以解决部分问题,这个可以后续再进一步了解。