给出问题:星巴克咖啡连锁店订单系统。这是个需要升级的系统,客户要求可以在咖啡中加入各种调料,例如:蒸奶、豆浆、摩卡等,星巴克会根据加入的调料收取不同的费用,所以升级的订单系统要考虑到这部分。下面给出他们之前的系统。
-
Beverage
description
getDescription();
cost();
//其他方法。。。
说明:Beverage是一个抽象类,店内所出售的饮料都必须继承此类。
description是一个实例变量,每个子类有不同表述。
getDesciption()很简单,就是为了返回对应子类的表述。
cost()方法是一个抽象方法,每一个子类必须实现此方法,来得出某一种饮料的价格。
分析:这个订单系统会带来什么问题?由于每个子类都要继承Beverage,每种调料也要这样。如果有很多子类,那么这个系统就会有成千上万的类!
解决方法:没有必要使用这么多类,利用实例变量和继承就可以追踪这些调料。下面是一个解决方法:
-
Beverage
description
milk
soy //黄豆
mocha
whip
getDescription();
cost();
hasMilk();
setMilk();
hasSoy();
setSoy();
hasMocha();
setMocha();
现在,Beverage类中的cost()不再是一个抽象方法,我们在这里给出了实现。让它加入了各种饮料的调料价钱,子类仍将覆盖cost(),但是会调用超类的cost(),计算出基本饮料加上调料的价钱。
这个方法似乎解决了“类爆炸”的问题,但是,调料价钱的改变会促使我们修改现有的代码,新增加了调料也会促使我们修改我们的代码,如果用户想要在一份咖啡中添加双份的牛奶,我们的设计还存在着问题。
这时候我们先引入第五个设计原则:类应该对扩展开放,对修改关闭。
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可以搭配新的行为,这样的设计的好处能使我们的软件具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。但是,我们没有必要为了遵循这个原则而使我们的工作量无谓的增加。在今后的工作中,还是需要我们积极思考,努力探索出比较均衡的方法。
OK,是时候给出装饰者模式的定义了:装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
回到星巴克咖啡订单系统,加入用户想要摩卡和豆浆深焙咖啡。我们做法就是以深焙咖啡为主题,然后在运行时以摩卡和豆浆来“装饰”深焙咖啡。如下:
1)以深焙咖啡(DarkRoast)开始.
-
DarkRoast
cost();
DarkRoast继承自Beverage,且有一个用来计算价格的方法cost().
2)顾客想要加上摩卡(Mocha),所以建立一个摩卡对象,并用这个对象将DarkRoast对象包进来。
-
Mocha
cost();
DarkRost
3)顾客想要豆浆(Soy),所以建立一个豆浆对象,并用豆浆对象将Mocha对象包进来。
-
Soy
cost();
Mocha
4)现在开始给顾客结帐的时候了。通过调用最外层装饰者(Soy)就可以办到。Soy的会先委托它装饰的对象Mocha计算出价钱,然后用Soy自身的加个加上Mocha的价格就得了顾客点的这杯咖啡的价钱了;同样的,Mocha在计算价钱的时候,也会先委托它装饰的对象DarkRoast计算出价钱,然后用自身的价钱加上DarkRoast的价格。
下面就是我们要把设计变成真正代码的时候了。这敲代码之前,还有一些东西需要说明。装饰者和被装饰者必须是同一类型,也就是有同一的超类,这是相当关键的地方,在下面的代码里,我们利用继承来达到“类型匹配”,而不是利用继承获得“行为”。
先从Beverage类下手,我们不需要改变他们之前的设计,保留:
public abstract class Beverage{
String description=”Unkown Beverage”;
public String getDescription(){
return description;
}
public abstract double cost();
}
Beverage类很简单,让我们来实现Condiment(调料)抽象类,也就是装饰者类吧:
public abstract class Condiment{
public abstract String getDescription();
}
所有调料装饰者都必须重写getDescription()方法。稍后我们会解释为什么。
下面我们开始写深焙咖啡:
public class DarkRoast extends Beverage{
public DarkRoast(){
description=”DarkRoast”;
}
public double cost(){
return 0.99;
}
}
实现了具体组件(DarkRoast),我们来实现具体装饰者Mocha:
public class Mocha extends Condiment{
Beverage beverage;
public Mocha(Beverage beverage){
this.beverage=beverage;
}
public String getDescription(){
return beverage.getDescription()+”,Mocha”;
}
public double cost(){
return beverage.cost()+0.20;
}
}
接下来实现豆浆这个装饰者:
public class Soy extends Condiment{
Beverage beverage;
public Soy(Beverage beverage){
this.beverage=beverage;
}
public String getDescription(){
return beverage.getDescription()+”,Soy”;
}
public double cost(){
return beverage.cost()+0.15;
}
}
最后,我们来测试下用户点的摩卡和豆浆深焙咖啡。
public class StarbuzzCoffee{
public static void main(String[] args){
Beverage beverage=new DarkRoast();
beverage=newMocha(beverage);
beverage=newSoy(beverage);
System.out.println(beverage.getDescription()+” $”+beverage.cost());
}
}
输出结果应该很明了了。
-
-
-
-