版权声明:本文由 Hov 所有,发布于 https://chenhy.com ,转载请注明出处。
1 装饰者模式
1.1 简介
装饰者模式可动态地向对象添加额外的属性或行为。装饰者模式是类继承的另外一种选择,类继承在编译时候增加行为,而装饰模式在运行时增加行为。在对象功能扩展方面,装饰者模式比继承更有弹性。
1.2 思路
通过增加一个装饰类包裹原来的类,包裹的方式一般是通过将原来的对象作为装饰类的构造函数的参数,从而在原有对象上添加新的功能。
1.3 使用场景
在不影响其它对象的情况下,想要动态地给单个对象添加新的属性或功能。例如,Java IO 就采用了装饰者模式。
1.4 优缺点
优点:
- 可用一个或多个装饰者包装一个对象,扩展性强;
- 对象可以在任何时候被修饰,即动态修饰对象;
- 非继承,避免子类爆炸。
缺点:
- 出现额外的装饰类,使程序变得复杂。
2 实例
模拟咖啡店:
假设,咖啡店有两种咖啡可选: Decaf 和 Espresso ,两种配料可选: Chocalate 和 Milk 。
不同的咖啡和配料可以搭配成不同的饮品,比如 Decaf 、 Espresso 、 Decaf + Chocalate 、 Decaf + Milk 、 Espresso + Chocalate 、 Espresso + Milk 六种不同的饮品。
我们要写个程序来计算不同饮品的价格,比如客户点了一杯 Espresso + Chocalate ,输出饮品的价格和描述信息。
2.1 糟糕的解决方案
一种解决方案是设计父类 Drink ,子类 Decaf 、 Espresso 、 Decaf + Chocalate 、 Decaf + Milk 、 Espresso + Chocalate 、 Espresso + Milk (对应不同的饮品)继承自父类 Drink 。
显然,每种组合的饮品都要写成一个子类,这样将导致子类爆炸。如果有 1000 种不同的饮品,那父类 Drink 下面就有 1000 个子类,这是绝对不能允许的。
这样的方案虽然设计简单,只需编写父类 Drink ,然后将所有不同的饮品通通继承自父类 Drink 即可。但缺点是显而易见的,1000 种饮品将对应 1000 个子类。
本方案类图如下:
父类 Drink 下面继承了各种各样的饮品子类,眼花缭乱。
2.2 稍好的解决方案
另一种方案是将不同的配料写入父类 Drink 中,子类通过 setter 来添加不同配料。
- 在父类 Drink 中定义 hasChocalate 、 setChocalate 、 hasMilk 、 setMilk 方法;
- Decaf 、 Espresso 作为父类 Drink 的子类;
- 当需要 Decaf + Chocalate 就调用父类的 setChocalate 方法添加 Chocolate ,再通过 hasChocalate 方法计算总价。
这种方案比上一种方案好一些,显然它不会引起子类爆炸。但是,因为配料相关的代码都编写在父类 Drink 中,如果我们要增加一种新的配料,就必须要修改父类 Drink ,这样维护性不好。
本方案类图如下:
父类 Drink 中包括不同配料的 setter 方法,子类只需调用相应的 setter 方法组合不同的配料。
2.3 基于装饰者模式的解决方案
基于装饰者模式,咖啡作为主体(被装饰者),配料作为装饰者。通过装饰类包裹主体,从而实现主体的动态扩展。
(1)父类 Drink
首先创建父类 Drink ,其包含饮品的基本属性(描述、价格),并定义了 setter 和 getter 方法,以及抽象方法 cost 。
/* Drink.java */
public abstract class Drink {
public String description = ""; //描述
private float price = 0f; //价格
public abstract float cost();
public String getDescription() {
return description+"-"+this.getPrice();
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
(2)被装饰者(ConcreteComponent)
前面我们提到咖啡店有 Decaf 、 Espresso 两种咖啡,其实咖啡就是主体(被装饰者),我们可以往咖啡加入不同的配料“装饰”成不同的饮品。
Decaf 、 Espresso 继承自 Coffee , Coffee 继承自 Drink 。也就是说,主体下有两种类型的咖啡,以后我们新增一种类型的咖啡 LongBlack ,只需让 LongBlack 像 Decaf 那样继承自 Coffee 即可,无需修改父类 Drink 。
中间层 Coffee :
/* Coffee.java */
public class Coffee extends Drink { //主体中间层
@Override
public float cost() { //返回价格
return super.getPrice();
}
}
被装饰者 Decaf :
/* Decaf.java */
public class Decaf extends Coffee { // Decaf 咖啡(被装饰者)
public Decaf() {
super.setDescription("Decaf"); //描述
super.setPrice(3.0f); //价格
}
}
被装饰者 Espresso :
/* Espresso.java */
public class Espresso extends Coffee { // Espresso 咖啡(被装饰者)
public Espresso() {
super.setDescription("Espresso"); //描述
super.setPrice(4.0f); //价格
}
}
(3)装饰者(Decorator)
咖啡可以添加不同的配料,如牛奶、巧克力等等。牛奶、巧克力就是装饰者,通过它们包装被装饰者,从而使被装饰者实现动态扩展。
中间层 Decorator :
/* Decorator.java */
public class Decorator extends Drink { //中间层
private Drink Obj; //被装饰的对象
public Decorator(Drink Obj) {
this.Obj = Obj;
};
@Override
public float cost() {
return super.getPrice()+Obj.cost(); //计算价格,要加上被装饰对象原有的价格
}
public String getDescription() {
return super.description + "-" + super.getPrice() + " && " + Obj.getDescription(); //递归增加原有属性
}
}
装饰者 Chocolate :
/* Chocolate.java */
public class Chocolate extends Decorator { //巧克力(装饰者)
public Chocolate(Drink Obj) {
super(Obj);
super.setDescription("Chocolate"); //描述
super.setPrice(3.0f); //价格
}
}
装饰者 Milk :
/* Milk.java */
public class Milk extends Decorator { //牛奶(装饰者)
public Milk(Drink Obj) {
super(Obj);
super.setDescription("Milk"); //描述
super.setPrice(2.0f); //价格
}
}
至此,我们已经完成了基础代码的编写。
我们设计了一个父类 Drink ,两个中间层 Coffee 、Decorator ,两个继承自中间层 Coffee 的具体实现类 Decaf 、Espresso ,两个继承自中间层 Decorator 的具体实现类 Chocolate 、Milk 。
代码结构如下:
(4)测试
现在,咖啡店来了两个订单。
订单 1 :客户单点了一杯 Decaf ;
订单 2 :客户点了一杯 Espresso + Milk + Chocolate + Chocolate 。
我们需要分别输出两个订单的饮品价格、饮品描述。
其中,订单 2 结构如下:
订单模拟程序:
/* CoffeeBar.java */
public class CoffeeBar {
public static void main(String[] args) {
Drink order; //订单
//订单 1 :只点Decaf,不加任何配料
order = new Decaf(); //点一杯Decaf
System.out.println("Order 1 Price:\n"+order.cost()); //输出饮品价格
System.out.println("Order 1 Desc:\n"+order.getDescription()); //输出饮品描述
System.out.println("******************");
//订单 2 :点一杯 Espresso,加一份 Milk 、两份 Chocolate
order = new Espresso(); //点一杯Espresso
order = new Milk(order); // Milk 装饰 Espresso(加一份 Milk )
order = new Chocolate(order); // Chocolate 装饰 Espresso + Milk(再加一份 Chocolate )
order = new Chocolate(order); // Chocolate 装饰 Espresso + Milk + Chocolate(再加一份 Chocolate )
System.out.println("Order 2 Price:\n"+order.cost()); //输出饮品价格
System.out.println("Order 2 Desc:\n"+order.getDescription()); //输出饮品描述
}
}
程序运行结果:
输出了不同订单对应的价格和描述。
Order 1 Price:
3.0
Order 1 Desc:
Decaf-3.0
******************
Order 2 Price:
12.0
Order 2 Desc:
Chocolate-3.0 && Chocolate-3.0 && Milk-2.0 && Espresso-4.0
3 结语
前面我们通过了一个实例来讲解装饰者模式。不同于直接使用继承,装饰者通过装饰类包裹被装饰对象实现扩展。
具体到例子中,两种咖啡(Decaf、Espresso),两种配料(Chocolate、Milk)。现在我们要实现 Decaf + Milk 的组合,常规的做法是 Decaf + Milk 直接继承自 Drink 这个父类,从而具备饮品的相关属性,但这样会引起子类爆炸。
通过装饰者模式,我们不直接使用继承。通过 Coffee 、 Decorator 作为中间层, Coffee 继承自父类 Drink ,显然 Coffee 具有基本的饮品属性(如 Decaf 、Espresso)。我们想要在 Decaf 上加入 Milk ,只需用 Decorator 中的 Milk 去包裹 Decaf 对象,从而 Decaf 对象增加了 Milk 相关的属性。
这就是装饰者模式实现动态扩展的思路。