装饰者模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构性模式,它是作为现有的类的一个包装,这种模式创建了一个装饰类,用来包装原有的类,并保证类方法签名完整的前提下,提供额外的功能。
意图:动态的给对象添加一些额外的职责,在不想增加很多子类的情况下扩展类,将具体功能职责划分,同时继承装饰者模式。
主要解决的问题:一般来说我们扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
使用场景:在不影响其它对象的情况下,以动态透明的方式给单个对象添加职责,当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护是可以使用,总之就是一句话 — — 扩展一个类的功能,并动态增加功能,动态撤销功能。
声明:这里是根据 Head First 一书中的例子来具体演示,进行了相应的总结,大家也可以去参考 Head First,进行更详细的了解
我们具体来分析一下上面的类图,假设我们去星巴克喝咖啡饮料,不同的咖啡饮料价格可能不一样,如果我们再加些调料,则还需要根据加不同种类的调料重新计价,这里 Beverage (咖啡饮料) 相当于抽象的 Component 类,三个具体组件(HouseBlent、Espresso、DarkRoast)每个代表一种咖啡类型。这里调料装饰者(Mocha、Soy、Whip)请注意:它们除了必须实现 cost() 之外,还必实现 getDescription()。
通过类图我们看到 CondimentDecorator 扩展自 Beverage 类,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的超类,这是相当关键的地方,这里我们利用继承达到“类型匹配”,而不是利用继承获得行为。
上面我们知道为何装饰者需要和被装饰者有相同的“接口”,因为装饰者必须能取代被装饰者,但是我们刚才提到的行为又是从哪里来的?其实当我们将装饰者与组件组合时,就是在加入新的行为,所得到的新行为并不是继承自超类,而是由组合对象得来的。
总结一下就是:继承 Beverage 抽象类,是为了有正确的类型,而不是继承它的行为,行为来自装饰者和基础组件,或与其它装饰者之间的组合关系,正是如此,因为使用组合对象,可以把所有咖啡饮料喝调料更有弹性的加以混合和匹配。
最后,我们这里想一下,如果我们需要继承的是 component 类型,为什么不把 Beverage 类设计成一个接口,而是设计成一个抽象类呢?其实这就是很多情况下,我们拿到代码时,Beverage “已经” 是一个抽象类了,通常装饰者模式是采用抽象类,但是 Java 中可以使用接口,尽管如此,通常我们都努力避免修改现有的代码,所以如果是抽象类运作的非常好,还是别去修改它。
接下来我们需要把设计变成代码来实现了
1、先从 Beverage 类下手,代码如下所示:
/**
* Beverage 是一个抽象类 Beverage 相当于 component 类
*
* @author qiudengjiao
*/
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost();
}
Beverage 类是一个抽象类,有两个方法,getDescription() 和 cost(),getDescription() 已经在此实现了,但是 cost() 必须在子类中实现。
2、接下来我们来实现 CondimentDecorator (调料) 抽象类,也就是装饰者类:
/**
* 装饰者类(调料)
*
* @author qiudengjiao
*
*/
public abstract class CondimentDecorator extends Beverage {
/**
* 所有的调料装饰者都必须实现 getDescription() 大家可以先考虑下,稍后我们会说
*/
public abstract String getDescription();
}
首先,必须让 CondimentDecorator 能够取代 Beverage,所以将 CondimentDecorator 扩展自 Beverage,原因我们上面已经提到过,其次所有的调料装饰者都必须实现 getDescription() 方法,稍后我们解释为什么。
3、写咖啡饮料的代码
现在,已经有了基类,我们可以来实现一些具体咖啡饮料,先从浓缩咖啡开始,别忘了我们需要为具体的咖啡饮料设置描述,而且还必须实现 cost() 方法
/**
* 浓缩咖啡
*
* @author qiudengjiao
*
*/
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso(浓缩咖啡)";
}
@Override
public double cost() {
return 1.99;
}
}
首先让 Espresso 扩展自 Beverage 类,因为 Espresso 是一种咖啡饮料,为了设置咖啡饮料的描述,我们写了一个构造器, description 实例变量继承自 Beverage,最后需要计算 Espresso 的价钱,现在不需要添加调料的价格,直接把 Espresso 的价格1.99 返回即可。
接下来我们来建立另外两种咖啡饮料类(HouseBlend、DarkRoast),和 Espresso 做法都是一样的,不在具体说明,代码如下:
/**
* 综合咖啡
* @author qiudengjiao
*
*/
public class HouseBlend extends Beverage {
public HouseBlend (){
description = "HouseBlend( 综合咖啡)";
}
@Override
public double cost() {
return 0.99;
}
}
/**
* 焦炒咖啡
*
* @author qiudengjiao
*
*/
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast(焦炒咖啡)";
}
@Override
public double cost() {
return 0.99;
}
}
4、写调料代码
如果你再看看装饰者类图,将发现我们已经完成了抽象组件(Beverage),有了具体组件(HouseBlent、Espresso、DarkRoast),也有了抽象装设者(CondimentDecorator),现在我们来实现具体装饰者,先从 Mocha (摩卡) 下手,代码如下:
/**
* 具体装饰者类(Moche:摩卡)
*
* @author qiudengjiao
*/
public class Moche extends CondimentDecorator {
Beverage beverage;
public Moche(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + "+ Moche(摩卡)";
}
@Override
public double cost() {
return 0.20 + beverage.cost();
}
}
摩卡(Mocha)是一个具体装饰者,所以继承自 CondimentDecorator 类,别忘了 CondimentDecorator 类扩展自 Beverage,家挨下来需要让 Mocha 引用一个 Beverage,具体做法如下:
1)用一个实例变量记录咖啡饮料,也就是被装饰者
2)想办法让被装饰者(咖啡饮料)被记录到实例变量中,这里的做法是把咖啡饮料当做构造器的参数,再由构造器将此咖啡饮料记录在实例变量中
具体到 getDescription() 方法,我们希望叙述不只是描述咖啡饮料(例如 DarkRoast),而是完整的连调料都描述出来(例如:darkRoast + Mocha),所以首先利用委托的做法,得到一个叙述,让后在其后加上附加的叙述(例如 Mocha)。
要计算带 Mocha 咖啡饮料的价钱,首先把调用委托给被装饰者对象,以计算价钱,然后再加上 Mocha 的价钱,得到最后结果。
接下来我们来写下 Soy 和 Whip 调料的代码如下:
/**
* 具体装饰者类(Soy:豆浆)
*
* @author qiudengjiao
*/
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + "+ Soy(豆浆)";
}
@Override
public double cost() {
return 0.15 + beverage.cost();
}
}
/**
* 具体装饰者类(Whip:奶油)
*
* @author qiudengjiao
*/
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 0.10 + beverage.cost();
}
}
5、 供应咖啡
好了,这时候我们可以坐下来喝杯咖啡了,代码如下:
/**
* 咖啡饮料具体测试类
*
* @author qiudengjiao
*/
public class StarbuzzCoffee {
public static void main(String arg[]) {
// 定一杯 Espresso(浓缩咖啡),不需要调料,打印出它的描述与价钱
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + "=$" + beverage.cost());
// 定一杯 DarkRoast(焦炒咖啡),加摩卡,加奶油
Beverage beverage1 = new DarkRoast();
beverage1 = new Moche(beverage1);
beverage1 = new Whip(beverage1);
System.out.println(beverage1.getDescription() + "=$" + beverage1.cost());
// 定一杯 HouseBlend(综合咖啡),加豆浆,加摩卡,加奶油
Beverage beverage2 = new HouseBlend();
beverage2 = new Soy(beverage2);
beverage2 = new Moche(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription() + "=$" + beverage2.cost());
}
}
我们来看一下具体结果:
好了,我们今天就写到这里,如有错误请指出。