03装饰者模式
例子
星巴兹是以扩张速度最快而闻名的咖啡连锁店。因为扩张速度实在太快,他们着急更新订单系统,来匹配他们的饮料供应要求。
实现1---继承
购买咖啡时,也可以要求其中加入各种调料,例如:蒸奶,豆浆
很明显,星巴兹为自己制造了一个维护噩梦,如果牛奶的价钱上扬,怎么办?新增一种焦糖调料风味时,怎么办
调料价格改变会迫使我们更改现有代码。
新的调料会迫使我们添加新的方法,并改变超类中的cost方法。
可能有新的饮料。对于其中一些饮料 (冰茶?),调料可能不适合,然而,Tea (茶)子类仍将继承像hasWhip() (是否加奶询) 这样的方法。
顾客想要双倍摩卡,怎么办?
如果不通过继承,要怎么达到复用?
通过组合和委托,可以在运行时“继承”行为
当通过子类化继承行为时,行为是编译时静态设置的。另外所有子类必须继承相同的行为。
但是,如果可以通过组合扩展对象的行为,就可以在运行时动态地做这件事情。
通过这个技巧,就可以把多个新的责任添加到对象甚至包括超类的设计师没有想到的责任。不必碰他们的代码!
关于组合在维护代码方面的效果,你学到了什么?
通过动态地组合对象,可以通过编写新代码添加新的功能,而无须修改已有代码。因为没有改变已有代码,引进bug或导致意外副作用的机会大幅减少。
代码应该像夜晚的莲花一样 (对改变)关闭,像早晨的莲花一样(对扩展)开放。
设计原则五:开放-关闭原则
类应该对扩展开放,但对修改关闭;
目标:允许类容易扩展以容纳新的行为,而不用修改已有代码;
优点:可以弹性应对改变,有足够弹性接受新的功能来应对改变的需求。
在每个地方应用开放-关闭原则是浪费没有必要的,要专注于设计中最有可能改变的区域,然后在那里应用该原则。
装饰者模式
用装饰者构造饮料订单
1.以DarkRoast对象开始,【DarkRoast继承自Beverage,且有一个用来计算饮料价钱的cost()方法。】
2.顾客想要摩卡 (Mocha),所以建立一个Mocha对象,并用它将DarkRoast对象包(wrap)起来
Mocha对象是一个装饰者,它的类型“反映”了它所装饰的对象 (本例中,就是,指的就是Beverage)。
所以Mocha也有一个cost()方法。通过多态也可以把Mocha所包裹的任何Beverase当成是Beverage
3.顾客也想要奶泡 (Whip),所以需要建立一个Whip装饰者,并用它将Mocha对象包起来别忘了,DarkRoast继承自Beverage,且有一个cost()方法,用来计算饮料价钱。
4.现在,该是为顾客算钱的时候了。通过调用最外圈装饰者 (Whip) 的cost()就可以办得到。
关于装饰者
1.装饰者和被装饰对象有相同的超类型。
2.可以用一个或多个装饰者包装一个对象。
3.鉴于装饰者和被装饰者对象有相同的超类型,在任何需要原始对象(被包装的)的场合,可以传递一个被装饰的对象代替它。
4.装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。(关键点)
5.对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
装饰者模式
装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
实现2---装饰者模式
Beverage类
public abstract class Beverage{
String description="Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
实现抽象类:(装饰者)
继承的意义:
装饰者和被装饰者必须是一样的类型,也就是有共同的超类
这里利用继承达到“类型匹配”,而不是利用继承获得“行为”
装饰者需要和被装饰者(即被包装的组件)有相同的“接口”,因为装饰者必须能取代被装饰者
行为获得:
当我们将装饰者与组件组合时,就是在加入新的行为,所得到的新行为,并不是集成自超类,而是由组合对象得到的。
行为来自装饰者和基础组件,或与其他装饰者之间的组合关系
//首先必须让CondimentDecorator能够取代Beverage,所以将CondimentDecorator扩展自Beverage
public abstract class CondimentDecorator extends Beverage{
//每个装饰者将要包裹的Beverage;【我们使用Beverage超类来引用到Beverage;因此,装饰者可以包裹任何饮料】
Beverage beverage;
//所有的调料装饰者都必须重新实现getDescription方法
@Override
public abstract String getDescription();
}
Beverage实现类子类
从浓缩咖啡(Espresso)开始
//首先,让Espresso扩展自Beverage类,因为Espresso是一种饮料。
public class Espresso extends Beverage{
//为了要设置饮料的描述,我们写了一个构造器,description实例变量继承自Beverage
public Espresso(){
description="Espresso";
}
//最后,需要计算Espresso的价钱,现在不需要管调料的价钱,直接把Espresso的价格返回即可
@Override
public double cost() {
return 1.99;
}
}
装饰者实现类子类
我们已经完成了抽象组件(Beverage),有了具体组件(Espresso等),也有了抽象装饰者(CondimentDecorator)。现在, 我们就来实现具体装饰者;
先从摩卡(Mocha)下手:
//摩卡是一个装饰者,所以让它扩展自CondimentDecorator。别忘了,CondimentDecorator扩展自Beverage
public class Mocha extends CondimentDecorator{
public Mocha(Beverage beverage){
this.beverage=beverage;//该类继承了CondimentDecorator中的Beverage实例变量;来持有正在包裹的饮料
}
//返回加入调料后的描述
@Override
public String getDescription() {
return beverage.getDescription()+",Mocha";
}
//返回装饰后的价格
//首先调用委托给正在装饰的对象,计算价格;然后把摩卡价格添上去。
@Override
public double cost() {
return 0.20+beverage.cost();
}
}
Whip(奶泡)
public class Whip extends CondimentDecorator{
public Whip(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Whip";
}
@Override
public double cost() {
return 0.10+beverage.cost();
}
}
测试
class StarbuzzCoffee{
public static void main(String arg[]){
//不加调料的浓缩咖啡
Beverage beverage1=new Espresso();
System.out.println(beverage1.getDescription()+"$"+beverage.cost());
//双倍摩卡咖啡加奶泡
Beverage beverage2=new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()+"$"+beverage.cost());
}
}
真实世界的装饰者:Java I/O
装饰java.io类
总结
OO原则:
封装变化
组合优于继承。
针对接口编程,而不是针对实现编程。
为交互对象之间的松耦合设计而努力。
类应该对扩展开放,对修改关闭。【让关闭的部分和新扩展的部分隔离。】
装饰者模式:给对象动态附加额外的责任。对于扩展功能,除了子类化之外,装饰者提供了弹性的替代做法。
问题:
引入装饰者会增加实例化组件所需代码的复杂度。
一旦用了装饰者,不只要实例化组件,还要把它包裹进装饰者中,谁知道有几个装饰者。
要点
1.继承是扩展形式之一,但未必是达到弹性设计的最佳方式。在我们的设计中,应该允许行为可以被扩展,而无需修改已有代码。
2.组合和委托经常可以用来在运行时添加新行为。
3.装饰者模式提供了子类化扩展行为的替代品。
4.装饰者模式涉及一群装饰者类,这些类用来包装具体组件。
5.装饰者类反映了它们所装饰的组件类型(事实上,它们和所装饰的组件类型相同,都经过了继承或接口实现)。
6.装饰者通过在对组件的方法调用之前(或/和之后,甚至在那一刻)添加功能改变其组件的行为。(关键点)
7.你可以用任意数目的装饰者来包裹一个组件。
8.装饰者一般对组件的客户是透明的,除非客户依赖于组件的具体类型。
9.装饰者会导致设计中出现许多小对象,过度使用会让代码变得复杂。