星巴克咖啡店问题
因为扩张速度太快,他们准备更新订单系统,以合乎他们的饮料供应要求。
他们原来的类设计是这样的。 。 。 。 。 。
Beverage //(饮料)是一个抽象类,店内所提供的饮料都必须继承自此类
description //这个变量,由子类设置,用来描述饮料,例如“超优深焙(Dark roast)咖啡豆”
getDescription()//返回description的内容
cost() //抽象方法,返回饮料的价钱,每个子类必须实现
子类一 子类二 子类三 子类四
HouseBlend DarkRoast Decaf Espresso
cost() cost() cost() cost()
购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶(Steamed Milk)、豆浆(soy)、摩卡(Mocha)或覆盖奶泡、星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料部分。
这是他们的第一个尝试. .. . . .
每个cost()方法将计算出咖啡加上订单上各种调料的价钱。很明显,如果牛奶的价格上扬,那么所有的用到牛奶的类都需要修改代码;
这样设计太复杂了,我们利用实例变量和继承,追踪这些调料来试试,先从Beverage基类下手,加上实例变量,代表是否加上调料(牛奶、豆浆、摩卡、奶泡. . . . . .)
Beverage
---------------------------
descrip
milk //各种调料的新的布尔值
soy
mocha
whip
getDesc()
cost() //不再是一个抽象方法,它计算要加入各种饮料的调料价钱,子类仍将覆盖cost,但是会调用超类的cost,计算出羁绊饮料加上调料的价钱。
hasMilk()//这些方法取得和设置调料的布尔值
setMilk()
hasSoy()
setSoy()
hasMocha()
setMocha()
hasWhip()
setWhip()
//其它有用的方法
-----------------------------------------------------
加入子类,每个类代表菜单上的一种饮料,每个cost()方法需要计算该饮料的价钱,然后通过调用超类的cost(),加入调料的价钱
子类一 子类二 子类三 子类四
HouseBlend DarkRoast Decaf Espresso
cost() cost() cost() cost()
这种方法只需要五个类,
但是调料价钱的改变,调料类就要修改
一旦出现新的调料,我们需要加上新的方法,并改变超类中的Cost()方法
以后可能会开发出新饮料,对这些饮料而言,某些调料可能并不合适,但是在这个设计方式中,Tea子类仍将继承哪些不适合的方法,例如:haswhip,
万一顾客想要双倍摩卡,怎么办?
认识装饰者模式
我们了解到利用继承无法完全解决问题:类数量爆炸、设计死板,以及基类加入的新功能并不适用于所有的子类。
那我们要以饮料为主体,然后运行时以调料来装饰decorate饮料。
1、以DarkRoast对象开始,DarkRoast继承自Beverage,且有一个用来计算饮料价格的cost方法。
2、顾客想要摩卡(Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包起来
3、顾客也想要奶泡(whip),所以建立一个Whip装饰者,并用它将Mocha对象包起来。DarkRoast继承自Beverage,且有一个cost方法,用来计算饮料价钱。
4、现在,该为顾客算钱了。通过调用最外圈装饰者(Whip)的cost就可以办得到。Whip的cost会先委托它装饰的对象(Mocha)计算出价钱,然后再加上奶泡的价钱。
装饰者模式
装饰者模式动态的将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰饮料框架
调料装饰者除了必须实现cost()之外,还必须实现getDescription()。
CondimentDecorator扩展自Beverage类,这样装饰者和被装饰者有共同的超类。
基类代码
先从Beverage类下手,这不需要改变星巴兹原始的设计。如下所示
public abstract class Beverage{
Stringdescription = “Unknown beverage”;
publicstring getDescription(){ //该方法已经实现,但是cost必须在子类中实现
returndescription;
}
publicabstract double cost();
}
写调料代码
实现Condiment调料抽象类,也就是装饰者类:
public abstract class CondimentDecoratorextends beverage {
publicabstract string getDescription();
}
写饮料代码
现在已经有了基类,开始实现一些饮料吧,先从浓缩咖啡(Espresso)开始。
public calss Espresso extends Beverage {
publicEspresso() {
description= “Espresso”; //设置饮料的描述,description继承自Beverage
}
publicdouble cost() {
return1.99;
}
public calss HouseBlend extends Beverage {
publicEs HouseBlend presso() {
description= “House Blend Coffee”;
}
publicdouble cost() {
return0.89; //另一种饮料
}
写调料代码
现在我们来实现具体装饰者。先从摩卡下手:
public class Mocha extendsCondimentDecorator {
Beveragebeverage; //用一个实例变量记录饮料,也就是被装饰者。
publicMocha(Beveragebeverage){
this.beverage = beverage;
}
publicString getDescription(){
returnbeverage.getDescription()+“,Mocha”;
}
publicdouble cost(){
return0.20+ beverage.cost();//Mocha的价钱加上被装饰者的价钱
}
}
供应咖啡
这是用来下订单的一些测试代码
public calss StarbuzzCoffee{
publicstatic void main(String args[]) {
Beveragebeverage = new Espresso(); //订一杯Espresso,不需要调料
System.out.println(beverage.getDescrioption()+”$”+beverage.cost());
Beveragebeverage2 = new DarkRoast();
beverage2= new Mocha(beverage2); //用Mocha装饰它
beverage2= new Mocha(beverage2); //用第二个Mocha装饰它
beverage2= new Whip(beverage2);
System.out.println(beverage2.getDescrioption()+”$”+beverage2.cost());
Beveragebeverage3 = new houseBlend();
beverage3= new Mocha(beverage3); //用Mocha装饰它
beverage3= new Mocha(beverage3); //用第二个Mocha装饰它
beverage3= new Whip(beverage3);
System.out.println(beverage3.getDescrioption()+”$”+beverage3.cost());
实验结果:
% java StarbuzzCoffe
Espresso $1.99
Dark Roast Coffe、 Mocha、Mocha、Whip $1.49
House Blend Coffee、Soy、Mocha、Whip $1.34
%