装饰者模式(Decorator Pattern)
关于装饰者模式,举个简单的例子来说明它的实现方式。
需求
大家在购买咖啡的时候,往往会依据自己的口味要求店员在咖啡中添加各种调料,例如:豆浆(Soy)、摩卡(Mocha,也就是巧克力风味)、奶泡(Whip)等。店员会根据所加入的调料而收取不同的费用。那么,使用面向对象编程,如何让这种需求变得可拓展呢?
问题难点在于:由于各种调料和各种咖啡的搭配不同,排列组合会有许多中品类产生,此时装饰者模式在这种场景下会大显身手。
看一个具体实现:
摩卡加奶泡的深焙咖啡
以咖啡饮料为主体,然后在运行时以调料来“装饰”(decorate)咖啡饮料。比如顾客想来一杯摩卡加奶泡的深焙咖啡,那么,实现思路如下:
- 拿一个深焙咖啡(DarkRoast)对象
- 以摩卡(Mocha)对象装饰它
- 以奶泡(Whip)对象装饰它
- 调用cost()方法,并依赖委托(delegate)将调料的价钱加上去
总体设计图
装饰者模式动态地将责任附加到对象上,若要拓展功能,装饰者提供了比继承更有弹性到替代方案。
代码实现
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:21
* 饮料抽象类
*/
public abstract class Beverage {
String description="Unknown Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:23
* 调料抽象类
*
* 必须让Condiment Decorator能够取代Beverage,所以将Condiment Decorator拓展自Beverage类
*/
public abstract class CondimentDecorator extends Beverage {
//所有的调料装饰者都必须重新实现getDescription()方法
public abstract String getDescription();
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:29
*
* 浓缩咖啡
* 是一种饮料,拓展自Beverage(饮料类)
*/
public class Espresso extends Beverage {
public Espresso(){
description="Espresso";//为了要设置饮料的描述,我们写了一个构造器。description实例遍历继承自Beverage
}
@Override
public double cost() {
//要计算Espresso的价钱
return 1.99;
}
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:33
* 另一种饮料,做法和Espresso一样,名称不同
*
*/
public class HouseBlend extends Beverage {
public HouseBlend(){
description="House Blend Coffee ";
}
@Override
public double cost() {
return 0.89;
}
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:35
*
* 摩卡
* 装饰者
*/
public class Mocha extends CondimentDecorator {
//用一个实例记录饮料,也就是被装饰者
Beverage beverage;
public Mocha(Beverage beverage){
//要让Mocha能够引用一个Beverage
//1.用一个实例变量记录饮料,也就是被装饰者
//2.想办法让被装饰者(饮料)被记录到实例变量中,这里的做法是:把饮料当作构造器的参数。再有构造器将饮料
//记录在实例变量中
this.beverage=beverage;
}
public String getDescription(){
//我们希望叙述不只是描述饮料(例如"DarkRost"),而是完整得连调料都描述出来,(例如"DarkRost,Macha")
//所以首先需要利用委托的做法,得到一个叙述,然后在其后加上附加的叙述(例如"Mocha")
return beverage.getDescription()+",Mocha";
}
public double cost(){
//要计算带Mocha饮料的钱。首先把调用委托给被装饰对象,以计算价钱,然后再加上Mocha的钱,得到足后结果。
return .20+beverage.cost();
}
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:48
*
* Soy调料
*/
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Soy";
}
public double cost(){
return .77+beverage.cost();
}
}
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:50
*
* Whip 调料
*/
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage){
this.beverage=beverage;
}
@Override
public String getDescription() {
return beverage.getDescription()+",Whip";
}
public double cost(){
return beverage.cost()+.99;
}
}
压轴测试类,开始享受一杯自己喜欢的咖啡吧!
package decorator;
/**
* @author zhangjinglong
* @date 2019-11-13-20:52
*
* 测试类
* 开始享受一杯咖啡
*/
public class StartbuzzCoffee {
public static void main(String[] args) {
Beverage beverage=new Espresso();//订一杯Espresso,不需要调料,打印出它的描述与价格
System.out.println(beverage.getDescription()+" $"+beverage.cost());
Beverage beverage2=new HouseBlend();//创建一个HouseBlend对象
beverage2=new Mocha(beverage2);//用Mocha装饰它
beverage2=new Soy(beverage2);//用Soy装饰它
beverage2=new Whip(beverage2);//用Whip装饰它
System.out.println(beverage2.getDescription()+" $"+beverage2.cost());
}
}