本文先从装饰者模式的内容进行讲解,然后深入到java源码中用到装饰者模式的内容中去。
首先,使用head first设计模式书中的一个例子进行装饰者模式的讲解。
现在有一个名为Beverage(饮料)的抽象类,cost用于计算饮料各种调料的总费用。
public abstract class Beverage {
protected String description = "beverage";
public abstract double cost();
public String getDescription() {
return description;
}
}
饮料有很多种类,有蓝山咖啡、浓缩咖啡等,而不同的饮料所添加的调料也各不相同,有摩卡、奶油等。
此时让你算出各种饮料的花费,你会怎么做?
很多人会先想到继承的方式,每有一种饮料就写一个类继承抽象类,每个类中都有各自不同的cost方法,用于计算自己调料的总费用。但是这样会有什么问题呢?如果我有很多种饮料,很容易造成类爆炸。
那么更进一步呢,有人想可以设计一个超类继承抽象类,在超类中放入所有调料的成员变量并设置成布尔值,然后在cost方法中进行每种调料的判断和计算。这样子类在继承这个超类的时候只要设置调料的布尔值,cost方法就可以自动算出总费用了。
这的确解决了类爆炸的问题,但是又会带来新的问题。超类中包含了所有的调料,但是子类中并不是都需要这些调料,有些调料甚至根本不适合这种饮料,但是子类都继承过来了。而且还有一个很重要的问题就是没有遵循开闭原则,如果有新的调料出来,那就需要修改超类。
此时就轮到装饰者模式出场了,它将各种饮料看成被装饰者,各种调料看成装饰者,用各种调料去装饰饮料,类似下图所示,就好比俄罗斯套娃一般。
接下来我们来看看具体的实现:
public class DarkRoast extends Beverage{
public DarkRoast() {
description = "dark roast coffe";
}
@Override
public double cost() {
return 2;
}
}
DarkRoast代表一种饮料,它本身的费用是2,它是被装饰者的身份。
public abstract class CondimentDecorator extends Beverage{
@Override
public abstract String getDescription();
}
CondimentDecorator类用于子类声明,继承该类的子类代表的就是装饰者的身份。
public class Mocha extends CondimentDecorator{
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 0.2 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
}
Mocha类代表一种调料,它本身的费用是0.2,它是装饰者的身份。注意!这里很重要的一点是Mocha类中组合了Beverage类,它让代码变得更加灵活。因为继承是编译时静态决定的,而组合是运行时动态决定的。
public class Whip extends CondimentDecorator{
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
return 0.1 + beverage.cost();
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
}
再添加一个调料类Whip。
现在我们有了饮料和调料类了,是时候冲泡出一杯完整的咖啡了
public class DecoratorMain {
public static void main(String[] args) {
Beverage darkRoast = new DarkRoast();
darkRoast = new Mocha(darkRoast);
darkRoast = new Whip(darkRoast);
System.out.println(darkRoast.getDescription() + ", 费用:" + darkRoast.cost());
}
}
//dark roast coffe, Mocha, Whip, 费用:13.0
从上面代码中可以看到,给饮料加调料十分地方便,只需new一个调料类,然后传入被装饰对象就可以了。如果觉得不便理解的同学,可以手动调试一下,看一下调用的过程,理解起来还是蛮简单的。
这是具体的uml类图
装饰者模式——配合Java源码进行深入讲解(二)