0. 序
装饰模式–> 使用对象组合的方式,在运行时装饰类。 可以在不修改任何底层代码的情况下,给对象赋予新的职责
1. 咖啡店实例
问题所在:一个饮料抽象父类Beverage , 当子类过多时,出现类的爆炸性复杂
很明显,这是难以维护的,出现一个新的饮料就要去继承父类。
一种特例:双倍奶泡咖啡,算一种饮料,三倍奶泡咖啡,… 无尽种类的饮料出现了。
违反了两个原则???
- 面向实现编程
- 变化的部分没有抽离出,如cost()方法是变化的
原则:开发-闭合(免于改变 能够扩展)
目标:允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。
OK, 这里针对上述饮料的继承问题:类数量爆炸,设计死板,难以维护,且基类的功能不适用所有子类。
装饰模式如何装饰?假设顾客要摩卡和奶泡深培咖啡,以饮料为主体,使用调料来装饰:
-
拿一个深培咖啡对象DarkRoast(继承自Beverage)
-
以摩卡对象装饰Mocha
-
以奶泡装饰Whip
-
调用cost()方法,应依赖委托(delegate)将调料价格加上去
我的疑问:如何去装饰?如何去委托?什么是委托?
装饰模式我只记得JDK中的输入输出流是装饰模式下的。但是是如何去实现的?理解好理解:就是一层一层的包裹,但实现是什么情况?以及我最后在咖啡这个装饰下如何去计算总价???
这里的例子是外层的cost依赖里面的cost的结果。
**显然:**装饰者可以在被委托的装饰者的行为之前与之后,加上自己的行为,达到特定的目的。–>如何去实现???
装饰者与被装饰者超类相同。–> 要使装饰者能取代被装饰者,多态。
2. 定义装饰者模式
装饰者模式动态把职责附加到对象上。在扩展上,比继承更有弹性。
针对caffe实例的观察者类图:
显然:要把饮料当成被装饰者,调料当初装饰者。
Ques:既然面向接口编程,为什么不把beverage设计成接口,而要当作一个抽象类?
Ans:(书上的解答)从最初的程序开始,beverage就是一个抽象类,通常装饰者模式是采用抽象类,也可以使用接口。但是,**避免去修改现有的代码。**毕竟现有的代码一般问题少,修改后可能会产生新的问题。。。
3. 代码
这章节理论上理解是好理解,但之前没有实现过 我觉得实现还是有点难,故写一下代码。
/**
* 抽象基类
*/
public abstract class Beverage {
protected String description="Unknown Beverage";//protected是子类可以访问 无 是 默认情况 同一个包下的类可以访问
public String getDescription() {
return description;
}
public abstract double cost();
}
被装饰者
/**
* 浓缩咖啡
*/
public class Espresso extends Beverage {
public Espresso() {
description="Espresso--浓缩咖啡";
}
@Override
public double cost() {
return 1.99;
}
}
/**
* 混合--黑咖啡
*/
public class HouseBlend extends Beverage {
@Override
public double cost() {
return 0.89;
}
public HouseBlend() {
description="house blend coffee";
}
}
装饰者–抽象出一个基类
/**
* 装饰类 --> 调料
*/
public abstract class Condiment extends Beverage {
public abstract String getDescription();
public double cost() {
return 0;
}
}
装饰者
/**
* 装饰者 摩卡
*/
public class Mocha extends Condiment {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+" , Mocha";
}
public double cost(){
return 0.20+beverage.cost(); //价格计算从内到外
}
}
//其他装饰者类似
实现starbuzzcoffee
public class StarbuzzCoffee {
public static void main(String[] args) {
Beverage beverage=new Espresso();//浓缩咖啡 被装饰对象
System.out.println(beverage.getDescription()+" "+beverage.cost()); //Espresso--浓缩咖啡 1.99
//开始装饰
beverage=new Mocha(beverage);//加上Mocha
System.out.println(beverage.getDescription()+" "+beverage.cost());//Espresso--浓缩咖啡 , Mocha 2.19
beverage=new Sony(beverage);
System.out.println(beverage.getDescription()+" "+beverage.cost());//Espresso--浓缩咖啡 , Mocha , Sony 2.49
beverage=new Whip(beverage);
System.out.println(beverage.getDescription()+" "+beverage.cost());//Espresso--浓缩咖啡 , Mocha , Sony , Whip 2.9
//双倍Mocha
beverage=new Mocha(beverage);
System.out.println(beverage.getDescription()+" "+beverage.cost());//Espresso--浓缩咖啡 , Mocha , Sony , Whip , Mocha 3.1
}
}
实现小杯,中杯,大杯
public abstract class CupSize extends Beverage {
public abstract String getDescription() ;
@Override
public double cost() {
return 0;
}
}
public class SmallCup extends CupSize {
Beverage beverage;
@Override
public String getDescription() {
return beverage.getDescription()+" , 小杯 多收0.1";
}
@Override
public double cost() {
return beverage.cost()+0.1;
}
public SmallCup(Beverage beverage) {
this.beverage = beverage;
}
}
这个例子相当于是把杯子大小当成一种配料,但问题很明显:有三个类。书上的例子是在超类Beverage中加入size属性,然后只用一个SizeDe类来装饰,在里面判断size==small|mid|big来增加价格。
上述问题剩下:如何去委托?什么是委托?
我的理解:就如计算caffee价格,实际上 咖啡被配料委托,咖啡的价格最终是依赖配料的价格计算的。将caffee委托给配料,在配料(即装饰者)中加入caffee(组合)。这样就通过cost方法一层层调用得到价格。