设计模式——装饰者模式

Posted 0x3f3f3f3f

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式——装饰者模式相关的知识,希望对你有一定的参考价值。

案例引入装饰者模式

假设我们现在要实现一个咖啡管理系统,来计算不同咖啡的价格,和管理对每一种咖啡的描述

如上类图所示,$Beverage$是一个饮料的抽象类,该店所有饮料商品都应该继承这个类
$description$变量是子类自己定义的,用来描述当前饮料的。该描述通过$getDescription()$返回。
$cost()$方法是抽象方法,是用来计算饮料的价格,由每个子类自己实现。
而,在我们实际需求中,购买者可能根据个人口味要求在咖啡中添加各种调料,如奶,糖, 巧克力等等。而加的调料不同就会导致最后咖啡的价格不同,所以订单系统必须要考虑到这些调料的部分。按照上一个类图的思路,我们不难想到,把每一种添加调料的咖啡作为一个子类来实现$cost()$方法,这样没多一种调料就会多很多的排列组合情况,结果如下图所示。这就是类爆炸的情况发生,显然是不可取的。

为了解决上述类爆炸的情况,我们首先想到的解决方案是,在父类定义若干代表是否添加某调料的变量,然后再父类实现$cost()$方法,里边只需要判断代表某调料的变量值是否为$true$,为$true$则计算上该调料的价格,计算完成后,返回计算出来的价格即可,而子类只需要调用父类的$cost()$方法,即可获取价格。
如下图类图所示

类中的$hasXxx()$和$setXxx()$方法是用来判断是否有某调料和设置该调料的方法。
我们知道,面向对象设计原则里有很重要的一条是对修改关闭, 对扩展开放
而如果按照上述方式设计类的话,难免会产生如下的问题。

  1. 如果调料的价格改变,则需要修改代码
  2. 如果出现新调料,就要加新的$hasXxx()$和$setXxx()$方法,并且要更改$cost()$方法
  3. 如果某饮料不适合该加该调料,但是继承的时候,加该调料的方法会被一并继承
  4. 如果某顾客想要加双倍的某调料,或者三倍的某调料则无法实现

因此,我们可以得出结论,这种设计方式也是不可取的。
为了解决这个场景,我们来使用装饰者模式

定义装饰者模式

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

装饰者模式来解决上述问题

我们以饮料为主体,然后运行时以调料来装饰饮料
例如:顾客想要摩卡和奶泡深焙咖啡,那么要做的是:

  1. 拿一个深焙咖啡$(DarkRoast)$对象
  2. 以摩卡$(Mocha)$对象装饰它
  3. 以奶泡$(Whip)$对象装饰它
  4. 调用$cost()$方法,并依赖委托将调料价格加上去

以装饰者构造饮料订单

  1. 以$DarkRoast$对象开始
  2. 建立一个$Mocha$对象,并用它将$DarkRoast$对象包起来

    $Mocha$对象是一个装饰者,他与所装饰对象的父类类型一致,因此它里边也有$cost()$方法。
  3. 顾客也想要奶泡,所以需要建立一个Whip装饰者,并用它将$Mocha$对象包起来

    $Whip$是一个装饰者,他与所装饰的对象类型的父类类型一致,因此它也包括一个$cost()$方法
  4. 所以在计算价格时,通过调用最外圈的装饰者的$cost()$方法就可以得到结果。

    所以实际调用流程是,最外层装饰者通过自身和内层调用结果算出总价格,内层调用者通过自身和被装饰者的调用结果算出本层的总价格,然后被装饰者调用自身方法返回价格。这样以一个递归的形式完成调用。

    我们可以得到的结论

  5. 装饰者和被装饰者有相同的父类类型
  6. 你可以用一个或多个装饰者包装一个对象
  7. 既然装饰者和被装饰者对象有相同的父类类型,所以在任何需要原始对象的场合,可以用装饰过他的对象代替。
  8. 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  9. 对象可以在任何时候被修饰,所以可以运行时动态的,不限量的使用你喜欢的装饰者来装饰对象。

我们总结上述有关装饰者模式的一些内容,可以得到一些代码结构


在该设计模式里,一共涉及到了四种角色

  1. 抽象组件:定义一个抽象接口,来规范准备附加功能的类
  2. 具体组件:将要被附加功能的类,实现抽象构件角色接口
  3. 抽象装饰者:持有对具体构件角色的引用并定义与抽象构件角色一致的接口
  4. 具体装饰:实现抽象装饰者角色,负责为具体构件添加额外功能

对于饮料来说的装饰者模式


其中的$Beverage$相当于抽象组件,而左侧的咖啡则是具体的组件。$CondimentDecorator$则是抽象装饰者,而底下的调料则是具体装饰。

实现装饰者模式

/**
 * 饮料类:抽象组件
 */
public abstract class Beverage 
    String description = "UnKown Beverage";

    public String getDescription() 
        return description;
    
    public abstract double cost();

/**
 * 调料类:抽象装饰者类
 */
public abstract class CondimentDecorator extends Beverage
    public abstract String getDescription();//所有的调料装饰者都必须实现这个方法
/**
 * 浓缩咖啡类:具体组件
 */
public class Espresso extends Beverage

    public Espresso() 
        description = "Espresso";//该变量继承自Beverage
    
    @Override
    public double cost() 
        return 1.99;
    

/**
 * 综合咖啡类:具体组件
 */
public class HouseBlend extends Beverage
    public HouseBlend() 
        description = "HouseBlend";
    
    @Override
    public double cost() 
        return 0.89;
    
/**
 * 摩卡调料:具体装饰者
 */
public class Mocha extends CondimentDecorator
    Beverage beverage;
    public Mocha(Beverage beverage) 
        this.beverage = beverage;//用一个实例变量记录饮料,也就是被装饰者
    
    @Override
    public double cost() 
        return beverage.cost() + 0.2;//首先把调用委托给被装饰对象,以计算价格,然后加上Mocha的价格
    

    @Override
    public String getDescription() 
        return beverage.getDescription() + ",Mocha";//首先利用委托的做法得到一个叙述,然后在其后加上附加的叙述
    

/**
 * 奶泡调料:具体装饰者
 */
public class Whip extends CondimentDecorator
    Beverage beverage;
    public Whip(Beverage beverage) 
        this.beverage = beverage;
    
    @Override
    public double cost() 
        return beverage.cost() + 0.8;
    

    @Override
    public String getDescription() 
        return beverage.getDescription() + ",Whip";
    

/**
 * 豆浆调料:具体装饰者
 */
public class Soy extends CondimentDecorator
    Beverage beverage;
    public Soy(Beverage beverage) 
        this.beverage = beverage;
    
    @Override
    public double cost() 
        return beverage.cost() + 0.5;
    

    @Override
    public String getDescription() 
        return beverage.getDescription() + ",Soy";
    
/**
 * 测试类
 */
public class StarbuzzCoffee 
    public static void main(String[] args) 
        Beverage beverage = new Espresso(); //一杯浓缩咖啡,不要调料
        System.out.println(beverage.getDescription() + " " + beverage.cost());//Espresso 1.99

        Beverage beverage1 = new HouseBlend();//一杯综合咖啡
        beverage1 = new Mocha(beverage1);//用摩卡装饰
        beverage1 = new Mocha(beverage1);//用第二个摩卡装饰
        beverage1 = new Whip(beverage1);//用奶泡装饰
        beverage1 = new Soy(beverage1);//用豆浆修饰
        System.out.println(beverage1.getDescription() + " " + beverage1.cost());//HouseBlend,Mocha,Mocha,Whip,Soy 2.59
    

以上是关于设计模式——装饰者模式的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式之装饰者模式

4.装饰者模式

设计模式---装饰者模式

7 装饰者模式

装饰者模式

设计模式之装饰者模式