装饰者模式(Decorator Pattern)——1. 实现及原理

写在前面

本文介绍装饰者模式,使用Java实现。首先以一个例子入手直接上手使用装饰者模式,然后再就原理进行讲解。

实例

下图是校园奶茶店的菜单,可以根据自己的需求在相应的饮料(Beverage)中添加调料(Condiment),例如可以选择奶茶+1份红豆+2份绿豆+3份冰块(16元)组合或者只要绿茶(3元)。现在该奶茶店由于生意太火爆需要定做一套自动化结算系统,该系统能够根据用户的选择结算相应的价格。

装饰者模式(Decorator Pattern)——1. 实现及原理_第1张图片
image.png

首先,分析菜单,可以看出饮料和调料都有 描述(description)<描述指是什么饮料或调料>和 单价,另外,调料还有一个 份数(pieces)的属性。所以我们可以先抽象出来饮料类(Beverage)和调料类(Condiment)。Beverage有抽象方法 getDescription()cost(),Codiment继承Beverage且有其独特的方法 setPieces(),所有方法作用见注释,定义为抽象方法是为了让其子类必须实现它。

  • Beverage.java
package pre.huangjs.decorator3;

public abstract class Beverage {

    // 返回对该饮料或者调料的描述
    public abstract String getDescription();
    
    // 返回价格
    public abstract double cost();
}
  • Condiment.java
package pre.huangjs.decorator3;

public abstract class Condiment extends Beverage{
    
    // 设置需要的份数
    public abstract void setPieces(int pieces);
}

思考一下,继承Condiment需要实现几个方法? 【 getDescription() cost() setPieces() 】

奶茶+1份红豆+2份绿豆+3份冰块(16元)组合为例来构建我们的系统。首先我们先看看该组合的制作过程。

装饰者模式(Decorator Pattern)——1. 实现及原理_第2张图片
image.png

如图所示,

  1. 第一步我们需要一个奶茶对象
  • MilkTea.java
package pre.huangjs.decorator3;

public class MilkTea extends Beverage {

    @Override
    public String getDescription() {
        return "Milk Tea<5.0>";
    }

    @Override
    public double cost() {
        return 5.0;
    }

}
  1. 第二步,我们需要红豆(RedBeans)这个装饰者,用来把奶茶装饰为红豆奶茶,然后用红豆奶茶替换原先的奶茶,即将包装后的红豆奶茶赋值给图中变量beverage
  • 这里需要十分注意的点——为了能够实现红豆奶茶替换奶茶,那么它们需要是同一类型,如何实现呢?其实红豆这个对象是调料(Condiment),肯定要继承Condiment这个类,Condiment类又继承自Beverage类,那么红豆类肯定就和奶茶类同一类型。(如果不懂这段话的含义可以继续把实例看完,再不行请留言,说明我没写好)
  • RedBeans.java
package pre.huangjs.decorator3;

public class RedBeans extends Condiment {

    private int pieces; // 所需份数
    private Beverage beverage; // 装饰前的饮料
    
    /**
     * @param copies 需要的份数
     * @param beverage 待包装的饮料
     */
    public RedBeans(int copies, Beverage beverage) {
        setPieces(copies);
        this.beverage = beverage;
    }

    @Override
    public void setPieces(int pieces) {
        this.pieces = pieces;
    }

    @Override
    public String getDescription() {
        
        // 返回对装饰后饮料的描述,例如“奶茶+一份绿豆”的描述为“Milk Tea<5.0> + 1 pieces Red Beans<2.0 x 1>”
        return beverage.getDescription() + " + " + pieces + " pieces Red Beans<2.0 x " + pieces + ">";
    }

    @Override
    public double cost() {
        
        // 返回总计的费用 = 装饰前饮料的费用 + 一份绿豆的价格 x 所需的份数
        return beverage.cost() + 2.0 * pieces;
    }

}
  • 这里我们以计算红豆奶茶的总金额为例说明这一替换过程。
    装饰者模式(Decorator Pattern)——1. 实现及原理_第3张图片
    image.png

RedBeans类中定义了成员变量Beverage beverage,这一变量的作用是记录装饰前饮料的状态,这样就可以在装饰前的基础上添加新功能。例如计算红豆奶茶总金额就是通过beverage.cost()回溯得到奶茶的价格,然后加上红豆的价格。

  1. 接下来两步需要构建绿豆(GreenBeans)装饰者和冰块(IceCubes)装饰者,思路和前一步一样,直接给出代码。
  • GreenBeans.java
package pre.huangjs.decorator3;

public class GreenBeans extends Condiment {

    private int pieces;
    private Beverage beverage;
    
    public GreenBeans(int copies, Beverage beverage) {
        setPieces(copies);
        this.beverage = beverage;
    }

    @Override
    public void setPieces(int pieces) {
        this.pieces = pieces;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + " + pieces + " pieces Green Beans<3.0 x " + pieces + ">";
    }

    @Override
    public double cost() {
        return beverage.cost() + 3 * pieces;
    }

}
  • IceCubes.java
package pre.huangjs.decorator3;

public class IceCubes extends Condiment {

    private int pieces;
    private Beverage beverage;

    public IceCubes(int pieces, Beverage beverage) {
        setPieces(pieces);
        this.beverage = beverage;
    }

    @Override
    public void setPieces(int pieces) {
        this.pieces = pieces;
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + " + " + pieces + " pieces Ice Cubes<1.0 x " + pieces + ">";
    }

    @Override
    public double cost() {
        return beverage.cost() + 1.0 * pieces;
    }

}

好了,我们可以来一杯奶茶+1份红豆+2份绿豆+3份冰块了。

  • SchoolShopTest.java
package pre.huangjs.decorator3;

public class SchoolShopTest {

    public static void main(String[] args) {
        
        // 制作一杯奶茶
        Beverage beverage1 = new MilkTea();
        
        // 使用红豆来装饰奶茶
        beverage1 = new RedBeans(1, beverage1);
        
        // 使用绿豆来装饰红豆奶茶
        beverage1= new GreenBeans(2, beverage1);
        
        // 使用冰块来装饰绿豆红豆奶茶
        beverage1 = new IceCubes(3, beverage1);
        
        // 输出该饮料的描述
        System.out.println(beverage1.getDescription());
        
        // 输出该饮料的价格
        System.out.println("共计:" + beverage1.cost());
    }

}

结果为


image.png

这里我将GreenTea.javaBlackTea.java贴出来,可以参考。

  • GreenTea.java
package pre.huangjs.decorator3;

public class GreenTea extends Beverage {

    @Override
    public String getDescription() {
        return "Green Tea" + "<3.0>";
    }

    @Override
    public double cost() {
        return 3.0;
    }

}
  • BlackTea.java
package pre.huangjs.decorator3;

public class BlackTea extends Beverage{

    @Override
    public String getDescription() {
        return "Black Tea<3.0>";
    }

    @Override
    public double cost() {
        return 3.0;
    }

}

原理

  • 定义:动态地将责任附加到对象上。

  • 装饰者模式的组成、

    • Component 抽象组件——Beverage类
    • ConcreteComponent 具体组件——奶茶(MilkTea)、绿茶(GreenTea)、红茶(BlackTea)
    • Decorator 抽象装饰者——Condiment类
    • ConcretDecorator 具体装饰者——红豆(ReadBeans)、绿豆(GreenBeans)、冰块(IceCubes)
  • 如何构建装饰者模式

    1. 分析获得被装饰者和装饰者,提取共同特征形成Component(抽象组件)Decorator(抽象装饰者),注意Decorator需要继承Component,使其具有和Component相同的类型。
    2. 继承Component类,实现具体组件(具体被装饰者);继承Decorator类,实现具体装饰者
    • By the way,装饰者和被装饰者界限不是很清楚,我也可以认为调料是被装饰者,饮料是装饰者,这就需要依据逻辑关系修改代码。
  • 装饰者模式的特征

    1. 装饰者(ReadBeans、GreenBeans、IceCube)和被装饰者 (MilkTea、GreenTea、BlackTea)有着相同的超类(Beverage)
    2. 可以使用一个或多个装饰者装饰一个对象
    3. 动态增加新功能:例如需要奶茶+1份绿豆这一功能,并非在代码中新建一个MilkTeaWithGreenBeans类,而是在需要用到的时候使用绿豆去修饰奶茶

结语

  • 纵观我之前所构建的类,我觉得还可以优化的地方:
    • 可以不把饮料和调料的价格写死,可以让其从配置文件中读取价格,这样有价格变动时只需要修改配置文件。
  • 下节将介绍为什么使用装饰者模式?

你可能感兴趣的:(装饰者模式(Decorator Pattern)——1. 实现及原理)