星巴克(Starbucks)是美国一家连锁咖啡公司的名称,1971年成立,是全球最大的咖啡连锁店,其总部坐落美国华盛顿州西雅图市,早期 的星巴克由于扩展速度过快,所以他们要更新订单系统,以合乎他们的饮料供应要求.他们原先的类设计是这样的:
Beverage(饮料)是一个抽象类,cost()是一个抽象的方法,我们看到下面4个子类都实现了cost()方法,cost()方法表示饮料的价钱,而getDescription()用来描述饮料,
客户在购买咖啡时,也可以要求在其中加入各种调料,例如:蒸奶,豆浆,摩卡,或者其他调料,星巴克会根据所加入的调料收取不同的费用,所以订单系统必须考虑到这些调料部分.那么客户最后结算是根据咖啡+调料最终的总价格,比如有15种调料,那么就必须写15个子类去继承Beverage(饮料),这不是导致类太多,而且不易扩展,这就是使用继承带来的不好维护地方,于是开发人员就要想办法打破这噩梦,他们在想首先要判断客户是否加了某种调料,我要判断如果加了我就把对应的调料的价格加入到总价上,那么这个功能就放在父类上去做,而各种咖啡的价值则是由各种子类自己去实现,
public class Beverage {
public String description;
public boolean milk;
public boolean soy;
public boolean mocha;
public boolean whip;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isMilk() {
return milk;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
public boolean isSoy() {
return soy;
}
public void setSoy(boolean soy) {
this.soy = soy;
}
public boolean isMocha() {
return mocha;
}
public void setMocha(boolean mocha) {
this.mocha = mocha;
}
public boolean isWhip() {
return whip;
}
public void setWhip(boolean whip) {
this.whip = whip;
}
/**
* 计算歌各种调料的价格
* @return
*/
public int cost(){
int flavour = 0;
if(milk){
flavour+=5;
}
if(soy){
flavour+=2;
}
if(mocha){
flavour+=3;
}
if(whip){
flavour+=3;
}
return flavour;
}
}
HouseBlend.java
public class HouseBlend extends Beverage {
public int coffee;
public void setCoffee(int coffee) {
this.coffee = coffee;
}
@Override
public int cost() {
return coffee+super.cost();
}
}
上层调用:
public class Clent {
/**
* @param args
*/
public static void main(String[] args) {
HouseBlend houseBlend = new HouseBlend();
houseBlend.setDescription("不错的咖啡");
houseBlend.setCoffee(15);
houseBlend.setMilk(true);
houseBlend.setSoy(true);
System.out.println(houseBlend.getDescription()+"花了"+houseBlend.cost()+"美元");
}
}
结果:
这种设计比第一种好在不必要创建那么多子类,而是用boolean值去代替了要加入那种调料,类图如下:
这样的设计优于第一种使用继承的方式,但是这个设计的缺点在于变化不可控,比如节日新增加了新的调料,我们就要在Beverage类上添加一个变量,然后Beverage类中的cost方法也要改变,对后期的维护很不方便,这种设计违背了6大设计原则中的类应该对扩展开放,对修改关闭.也就是说新需求在不修改现有的代码情况下,但是可以添加类什么的来完成后期的需求变化,所以开发人员必须在此设计基础上再次对系统进行改造。
新系统设计思路:
我们要以饮料为主体,然后运行时以调料来 "装饰(decorate)"饮料,比方说,如果顾客想要摩卡和奶泡培咖啡,那么,要做的是:
第一步:拿一个深培咖啡(DarkRoast)对象
第二步:以摩卡(Mocha)对象装饰它
第三步:以奶泡(whip)对象装饰它
第四步:调用cost()方法,并依赖委托(delegate)将调料的价钱加上去,
思路想好了,但是具体到某个开发人员怎么去利用代码实现出来呢?首先如何装饰一个对象,而委托又要如何与此搭配使用呢?架构师点了一下:把装饰者对象当成包装者,苦逼的码农开始在想,首先得创建几个类,
DarkRoast(深培咖啡)
Mocha(摩卡),所以建立一个Mocha对象,并用它将DarkRoast对象包装起来,因为最后计算总价格,是咖啡的钱+饮料的钱,所以在这里要持有DarkRoast的引用,好调用cost()方法,
如果顾客一天没吃饭了,晚上想多吃点,所以顾客还想点一个奶泡(whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包起来,别忘了,DarkRoast继承自Beverage,并有一个cost()方法,用来计算饮料价钱,
当最后顾客结账的时候,通过调用最外层的Whip对象的cost()方法即可,Whip对象的cost()会先委托它装饰的对象(也就是Mocha)计算出价钱,然后再加上奶泡的价钱,
你会发现这些创建的类中都有一个公共的方法cost(),是的因为他们都继承自Beverage这个类,
通过上面的你会发现装饰者和被装饰者有如下几个关系:
1:装饰者和被装饰对象有相同的父类(Beverage)
2:可以用一个或多个装饰者包装一个对象,
3:既然装饰者和被装饰对象有相同的父类,所以在任何需要原始对象(被包装Beverage)的场合,可以用装饰过的对象代替它,
4:装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的.
5:对象可以在任何时候被装饰,所以可以在运行时动态地,不限量地用你喜欢的装饰者来装饰对象.
写到这里看下装饰模式的概念:
动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的代替方案.
它的类图如下:
1.Component(被装饰对象的基类)定义一个对象接口,可以给这些对象动态地添加职责。
2.ConcreteComponent(具体被装饰对象)定义一个对象,可以给这个对象添加一些职责。
3.Decorator(装饰者抽象类)维持一个指向Component实例的引用,并定义一个与Component接口一致的接口。
4.ConcreteDecorator(具体装饰者)具体的装饰对象,给内部持有的具体被装饰对象,增加具体的职责
现在用这个模式来解决星巴克订单系统,设计类图如下:为了验证系统是否具有扩展和维护,我们随便来个需求,客户点了一个"双倍摩卡豆浆奶泡拿铁咖啡":
代码如下:
Beverage.java
package com.decorate;
public abstract class Beverage {
String description = "";
public String getDescription() {
return description;
}
public abstract double cost();
}
CondimentDecorator.java
package com.decorate;
/**
* 调料被装饰抽象类
*/
public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
}
DrakRoast.java
package com.decorate;
import com.decorate.Beverage;
public class DrakRoast extends Beverage {
public DrakRoast(){
description = "DrakRoast";
}
@Override
public double cost() {
return 1.05;
}
}
Espresso.java
package com.decorate;
public class Espresso extends Beverage{
@Override
public double cost() {
return 1.99;
}
public Espresso(){
description = "Espresso";
}
}
HouseBlend.java
package com.decorate;
public class HouseBlend extends Beverage{
public HouseBlend(){
description = "HouseBlend";
}
@Override
public double cost() {
return 0.89;
}
}
Mocha.java
package com.decorate;
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 beverage.cost()+.20;
}
}
Whip.java
package com.decorate;
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage){
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Whip";
}
@Override
public double cost() {
return beverage.cost()+.20;
}
}
测试类:
package com.decorate;
/**
* 测试类
* @author admin
*/
public class Test {
public static void main(String[] args) {
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription()+":$"+beverage.cost());
Beverage beverage2 = new DrakRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()+":$"+beverage2.cost());
}
}
运行结果:
Espresso:$1.99
DrakRoast,Mocha,Mocha,Whip:$1.65
装饰模式就讲到这里了!
设计模式就是在特定的场景下解决什么问题而已,就是个套路!