注:本篇博客内容出自Head First设计模式,仅对文章进行整理与总结。
1. 问题引出
我们需要为一家咖啡店做一套新的订单系统,之前的设计如下图所示:
另外购买咖啡时还需要加入不同的调料,如蒸奶(Steamed Milk),豆浆(Soy),摩卡(Mocha)或者是奶泡并收取不同的费用等。
如果我们列出不同的搭配方案供客户选择,那么难以想象我们最终需要列出多少具体类,这也违反了我们之前的设计原则:针对接口编程,而不是针对具体实现编程。
还有一种实现就是使用成员变量和继承去追踪添加的不同调料,看下图:
如果按照这种方法实现,那么今后如果添加新的调料或者修改现有调料价格亦或是其他操作,我们就必须打开Beverage或者其子类修改代码,这样就会使得代码难以维护。
2 解决思路
我们的目标是允许类容易拓展,在不修改现有代码的情况下,就可以搭配新的行为,此例中就是搭配不同的调料。这样的设计具有弹性可以应对改变,可以接受新的功能来应对。
至此,我们引入一个新的设计原则:开放-关闭原则,即:
类应该对扩展开放,对修改关闭
简单来说就是对于已经设计好的类,不应该再去修改它,但是我们可以扩展它。举个不恰当的列子,比如之前的观察者模式中,我们对于Object的实现类,在运行时可以任意添加和删除Observer而不需要修改具体类。
对于现在的情况,我们需要计算咖啡和不同调料的价格来计算出饮品的售价。根据上面的原则,我们需要在运行时将不同的调料加入到咖啡中,而不是在程序运行前去修改具体的饮品类。
在策略模式中,我们利用组合和委托的方式,在运行时动态的赋予子类不同的行为,达到了类似继承的效果。利用继承的方式设计子类时,子类的行为是在运行前静态决定的,如果使用组合和委托就可以在运行时动态的扩展子类的行为,达到想要的效果。
所以,我们可以将咖啡和调料分为两大类族,这样就可以程序运行时指定特定的行为(调料)。
3 装饰者模式
下面是按照装饰者模式制作的一份摩卡加奶泡的深焙咖啡:
(1)拿一个深焙咖啡:
(2)以摩卡对象装饰它
(3)以奶泡对象装饰它
(4)最后计算价格
接下来实际写一些代码来实现上述流程。
再此之前先看一下观察者模式的定义和整体的设计框架。
观察者模式定义如下:
动态的将责任附加到对象身上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
整体设计框架如下:
从上图中我们看到CondimentDecorator类继承了Beverage,这里的继承是为了达到类型匹配的效果,而不是利用继承获得行为,且装饰者和被装饰者必须有想同的接口,这是因为装饰者必须能取代被装饰者,这个看代码会理解的深刻一些。下面开始写一些代码。
4 代码实现
1. 首先是父类Beverage:
/**
* This class use as base class of different beverage
* @author canvas
*
*/
public abstract class Beverage {
protected String description = "Unknow beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
2. 接下来是调料抽象类CondimentDecorator:
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
3.具体的Beverage子类:
//Espresso类
public class Espresso extends Beverage {
public Espresso() {
// TODO Auto-generated constructor stub
description = "Espresso";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 1.99;
}
}
//HouseBlend类
public class HouseBlend extends Beverage {
public HouseBlend() {
// TODO Auto-generated constructor stub
description = "HouseBlend";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return 0.89;
}
}
//.....其他类省略,可自行实现
4. 具体的调料子类:
public class Mocha extends CondimentDecorator {
private Beverage beverage;
public Mocha(Beverage beverage) {
// TODO Auto-generated constructor stub
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+" , Mocha";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return beverage.cost()+0.20;
}
}
public class Soy extends CondimentDecorator {
private Beverage beverage;
public Soy(Beverage beverage) {
// TODO Auto-generated constructor stub
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+" , Soy";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return beverage.cost()+0.30;
}
}
public class Whip extends CondimentDecorator {
private Beverage beverage;
public Whip(Beverage beverage) {
// TODO Auto-generated constructor stub
this.beverage = beverage;
}
@Override
public String getDescription() {
// TODO Auto-generated method stub
return beverage.getDescription()+" , Whip";
}
@Override
public double cost() {
// TODO Auto-generated method stub
return beverage.cost()+0.80;
}
}
5. 测试代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()+" , cost : "+beverage.cost());
Beverage beverage2 = new HouseBlend();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()+" , cost : "+beverage2.cost());
Beverage beverage3 = new Espresso();
beverage3 = new Mocha(beverage3);
beverage3 = new Soy(beverage3);
beverage3 = new Whip(beverage3);
System.out.println(beverage3.getDescription()+" , cost : "+beverage3.cost());
}
6. 输出结果
Espresso , cost : 1.99
HouseBlend , Mocha , Mocha , Whip , cost : 2.09
Espresso , Mocha , Soy , Whip , cost : 3.29
以上,结束。