博客信息

装饰器模式Decorator(结构模式)

发布时间:『 2019-03-24 03:36』  博客类别:23种设计模式  阅读(649)
  • 术语

    • Component:组件(主体)

    • concreteComponent:被装饰者

    • Decorator:装饰者

注意:concreteComponent、Decorator都会实现或继承Component

  • 角色

    • Client 类:装饰模式的调用者

    • Component:主体===》饮料(Drink)

    • concreteComponent:被装饰者===》咖啡(Coffee)

    • xxxConcreteComponent:具体的被装饰者(ChinaCoffee) 美式咖啡、欧式咖啡、中式咖啡

    • Decorator:装饰器===》调料

    • xxxDecorator:子装饰器 糖类、奶酪、牛油、芥末

  • 案例

    单体咖啡与调味组合的饮料计价项目

    • 使用前

    这个项目最容易想到的设计方案就是采用继承的设计方案,设计思路如下:

    Drink===》饮品

    Juice===》果汁

    .......

    Coffee===》咖啡

    ChinaCoffee===》中式咖啡

    ASeasoningChinaCoffee===》被A调料修饰的中式咖啡

    BSeasoningChinaCoffee===》被B调料修饰的中式咖啡

    ......

    XxxCoffee===》其它咖啡

    ASeasoningXxxCoffee===》被A调料修饰的其它咖啡

    BSeasoningXxxCoffee===》被B调料修饰的其它咖啡

    ......

    上面每个类中都有getPrice计价的功能,从结果上来看,确实可以完成项目需求,但是整个设计体系过于臃肿,不便于后期的扩展与维护;

    用前面的桥接模式来进行设计,可以解决体系臃肿问题

    package com.javaxl.design.decorator.before;

    /**
    * @author 小李飞刀
    * @site www.javaxl.com
    * @company
    * @create  2020-02-22 18:27
    *
    * 饮料包括单体咖啡+调料
    */
    public abstract class Drink {
      protected double price;
      protected int n;
      protected DrinkSeasoning seasoning;

      public abstract double getPrice();
    }

    /**
    * 单体咖啡
    */
    abstract class Coffee extends Drink {
    }

    /**
    * 单体果汁
    */
    abstract class Juice extends Drink {
    }

    class ChinaCoffee extends Coffee{
      ChinaCoffee(double price,int n){
          this.price = price;
          this.n = n;
      }

      @Override
      public double getPrice() {
          return this.price*this.n+this.seasoning.getPrice();
      }
    }


    package com.javaxl.design.decorator.before;

    /**
    * @author 小李飞刀
    * @site www.javaxl.com
    * @company
    * @create  2020-02-22 18:32
    *
    * 调料的抽象接口
    */
    public interface DrinkSeasoning {
      public abstract double getPrice();
    }

    /**
    * A类调料
    */
    class ADrinkSeasoning implements DrinkSeasoning{
      protected double price;
      protected int n;
      ADrinkSeasoning(double price,int n){
          this.price = price;
          this.n = n;
      }
      @Override
      public double getPrice() {
          return this.price*this.n;
      }
    }


    /**
    * B类调料
    */
    class BDrinkSeasoning implements DrinkSeasoning{
      private double price;
      protected int n;
      BDrinkSeasoning(double price,int n){
          this.price = price;
          this.n = n;
      }
      @Override
      public double getPrice() {
          return this.price*this.n;
      }
    }


    public class Client {
      public static void main(String[] args) {
          ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
          ADrinkSeasoning aDrinkSeasoning = new ADrinkSeasoning(2,2);
          chinaCoffee.seasoning = aDrinkSeasoning;
          System.out.println("中式咖啡1份+A调料2份,最终价格为:"+chinaCoffee.getPrice());

    //       思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
    //       思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
      }
    }

上面的代码存在问题:

思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢? 思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?

思考3:Drink饮品单体饮料种类多,调料种类也多,会带来什么问题?

Drink抽象类中聚合List<DrinkSeasoning>才可以解决上述的前两个问题;但是,在原有订单追加调料,相当于给原有对象进行装饰,这类的问题更加适合用装饰模式来解决;

  • 使用后

使用装饰模式进行设计

小李飞刀_设计模式

代码逻辑如下

package com.javaxl.design.decorator.after;

import com.javaxl.design.decorator.before.DrinkSeasoning;

/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create  2020-02-22 18:27
* <p>
* 饮料包括单体咖啡+调料
*/
public abstract class Drink {
  protected double price;
  protected int n;

  public abstract double getPrice();
}

/**
* 单体咖啡
*/
abstract class Coffee extends Drink {
}

/**
* 单体果汁
*/
abstract class Juice extends Drink {
}

class ChinaCoffee extends Coffee {
  ChinaCoffee(double price, int n) {
      this.price = price;
      this.n = n;
  }

  @Override
  public double getPrice() {
      return this.price * this.n;
  }
}


package com.javaxl.design.decorator.after;

/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create  2020-02-22 22:26
*/
public class DecoratorDrink extends Drink {
  private Drink drink;

  public DecoratorDrink(Drink drink, double price, int n) {
      this.drink = drink;
      this.price = price;
      this.n = n;
  }

  @Override
  public double getPrice() {
      return this.price * this.n + drink.getPrice();
  }
}

class ADecoratorDrink extends DecoratorDrink {
  public ADecoratorDrink(Drink drink, double price, int n) {
      super(drink, price, n);
  }
}

class BDecoratorDrink extends DecoratorDrink {
  public BDecoratorDrink(Drink drink, double price, int n) {
      super(drink, price, n);
  }
}


package com.javaxl.design.decorator.after;


/**
* @author 小李飞刀
* @site www.javaxl.com
* @company
* @create  2020-02-22 18:50
*/
public class Client {
  public static void main(String[] args) {
      ChinaCoffee chinaCoffee = new ChinaCoffee(6,1);
//       假定A类调料2元一份,B类调料3元一份
      Drink order = new ADecoratorDrink(chinaCoffee, 2, 2);
      System.out.println("中式咖啡1份+A调料2份,最终价格为:"+order.getPrice());

//       思考1:如果我要下单中式咖啡1份+A调料3份+B调料2份,计算出最终的价格,那代码该怎么改动呢?
      order = new ADecoratorDrink(order,2,1);
      System.out.println("式咖啡1份+A调料3份,最终价格为:"+order.getPrice());
      order = new BDecoratorDrink(order,3,2);
      System.out.println("式咖啡1份+A调料3份+B调料2份,最终价格为:"+order.getPrice());

//       思考2:在原有的咖啡订单下,追加B调料2份,计算出最终的价格,那代码该怎么改动呢?
      order = new BDecoratorDrink(order,3,2);
      System.out.println("式咖啡1份+A调料3份+B调料4份,最终价格为:"+order.getPrice());
  }
}


小李飞刀_设计模式



  • 注意事项及细节

    • 装饰者模式一般用于对原有功能进行增强/装饰

    • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性

  • 应用

    IO流体系:缓冲流


总结

   继承保证了父类和子类的一致性(有共同的方法),委托保证了使用委托的类和被委托对象的一致性。可以看到装饰模式中,保证了装饰边框与被装饰物体的一致性(有共同父类),使用了模板方法,这个方法几乎无处不在呀,同样使用了委托(组合),通过在原始数据上面一层层的包裹,最终得到了我们想要的输出,有着非常广泛的用处。


over......



关键字:     设计模式  

备案号:湘ICP备19000029号

Copyright © 2018-2019 javaxl晓码阁 版权所有