1.背景
现在有以下场景:咖啡店中有各种咖啡,而且各种咖啡可以加入不同的调料,比如蒸奶、豆浆、摩卡等,然后根据加入的调料收取相应的费用。现在要针对这样的使用要求设计出咖啡店中的各种饮料对象。
2.过程分析
书中提到了一种现有的设计方案,其类图如下:
其中Beverage为饮料的基类,店中所有的咖啡都继承自Beverage,并实现cost方法计算这种咖啡的费用,如现在有一种加了摩卡和奶泡的深焙咖啡,就需要建立个DarkRoastWithMochaAndWhip类,const方法中返回这种咖啡的价格。如果按照这种方法设计,那么店中有多少种咖啡的搭配方法,那么就有多少个咖啡类,显然是不能适用的。那么,针对这样的缺陷,又有一种设计方案:
所有的咖啡都继承Beverage基类,得到不同的description的属性,基类中已经保持有各种调料的状态,cost方法根据这些调料的有无来计算出价格,这样就可以大大减少类的数量,有几类咖啡就有几个类。如果需要加入一种调料,就调用相应的set方法,然后cost就能动态地计算出价格。看上去很符合要求。但是这样的设计却违反了一个重要的设计原则:开放-关闭原则。如果要增加一种新的调料,或者修改一种调料的价格,则要对Beverage进行相应的修改,而且对于调料的添加也不够灵活,比如添加两份牛奶,这样的设计就无法达到这样的要求。
3.定义
根据以上的分析,发现两种不同的方案有着各自的缺陷,先引出装饰者模式定义:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
4.装饰者模式类图
其中HouseBlend和DarkRoast是咖啡类型,需要被装饰,充当ConcreteComponentde的角色。Milk和Mocha是装饰者,用来装饰咖啡。所有的装饰者都要继承自CondimentDecorator,以表示他们是一个装饰者。而所有的装饰者和被装饰者都有一个共同的超类:Beverage(在装饰者模式中统称为Component),用来表示装饰者和被装饰者是同一类型,这样就可以在装饰者中通过引用Beverage来装饰所有的ConcreteComponentde,这样可以在被装饰者外面包含一层装饰者,外面又可以包含一层装饰者,这样一层层包含,达到动态修饰的目的。不同的装饰中会对他们的相关方法返回结果进行装饰,从而达到为该方法添加附加功能的能力。例如在Milk中调用beverage的cost方法,并在其返回值的基础之上+0.1,做为Milk自己的cost方法的返回值,这样,被Milk装饰后,调用cost方法后其值就会增加0.1。
5.设计原则
开放-关闭原则:对扩展开放,对修改关闭。就是说在设计的时候,要尽可能做到在以后的功能扩展中不去修改原有的代码,只是往系统中添加新的组件来完成需要的功能。
6.源代码
Beverage
public abstract class Beverage {
protected String description = "unknown";
public String getDescription(){
return description;
}
public abstract double cost();
}
CondimentDecorator
public abstract class CondimentDecorator extends Beverage{
public abstract String getDescription();
}
HouseBlend
public abstract class Beverage {
protected String description = "unknown";
public String getDescription(){
return description;
}
public abstract double cost();
}
Mocha
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ",Mocha";
}
@Override
public double cost() {
return 0.5 + beverage.cost();
}
}