写在前面
本文介绍装饰者模式,使用Java实现。首先以一个例子入手直接上手使用装饰者模式,然后再就原理进行讲解。
实例
下图是校园奶茶店的菜单,可以根据自己的需求在相应的饮料(Beverage)中添加调料(Condiment),例如可以选择奶茶+1份红豆+2份绿豆+3份冰块(16元)组合或者只要绿茶(3元)。现在该奶茶店由于生意太火爆需要定做一套自动化结算系统,该系统能够根据用户的选择结算相应的价格。
首先,分析菜单,可以看出饮料和调料都有描述(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元)组合为例来构建我们的系统。首先我们先看看该组合的制作过程。
如图所示,
- 第一步我们需要一个奶茶对象
- 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;
}
}
- 第二步,我们需要红豆(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;
}
}
- 这里我们以计算红豆奶茶的总金额为例说明这一替换过程。
RedBeans
类中定义了成员变量Beverage beverage
,这一变量的作用是记录装饰前饮料的状态,这样就可以在装饰前的基础上添加新功能。例如计算红豆奶茶总金额就是通过beverage.cost()
回溯得到奶茶的价格,然后加上红豆的价格。
- 接下来两步需要构建绿豆(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());
}
}
结果为
这里我将GreenTea.java
和BlackTea.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)
-
如何构建装饰者模式
- 分析获得被装饰者和装饰者,提取共同特征形成
Component(抽象组件)
和Decorator(抽象装饰者)
,注意Decorator
需要继承Component
,使其具有和Component
相同的类型。 - 继承
Component
类,实现具体组件(具体被装饰者);继承Decorator
类,实现具体装饰者
- By the way,装饰者和被装饰者界限不是很清楚,我也可以认为调料是被装饰者,饮料是装饰者,这就需要依据逻辑关系修改代码。
- 分析获得被装饰者和装饰者,提取共同特征形成
-
装饰者模式的特征
- 装饰者(ReadBeans、GreenBeans、IceCube)和被装饰者 (MilkTea、GreenTea、BlackTea)有着相同的超类(Beverage)
- 可以使用一个或多个装饰者装饰一个对象
- 动态增加新功能:例如需要奶茶+1份绿豆这一功能,并非在代码中新建一个
MilkTeaWithGreenBeans
类,而是在需要用到的时候使用绿豆去修饰奶茶
结语
- 纵观我之前所构建的类,我觉得还可以优化的地方:
- 可以不把饮料和调料的价格写死,可以让其从配置文件中读取价格,这样有价格变动时只需要修改配置文件。
- 下节将介绍为什么使用装饰者模式?