让我们首先来看一下装饰者模式的定义:
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
看到这个定义我们很容易就能想到两个问题:
- 责任是怎么动态地附加在对象上的呢?
- 装饰者模式哪里比继承好?
为了得到这两个问题的答案,下面我们就来看看UML图和具体的代码。
从上面的UML图可以看到,concreteComponent和Decorator继承了Component,其中concreteComponent是要动态加上新行为的对象,而Decorator则是具体的装饰者的父类,它可以是抽象类,也可以是接口。继承了Decorator的子类们,利用组合拥有component的实例变量(关键!),既可以扩展状态,也可以加上新的方法。
直接看UML图的话可能有点不知所云,接下来结合具体的代码吧。
Beverage beverage = new Espresso(); //向上转型
System.out.println(beverage.getDescription()
+ " $" + beverage.cost());
Beverage beverage2 = new DarkRoast();
beverage2 = new Mocha(beverage2);
beverage2 = new Mocha(beverage2);
beverage2 = new Whip(beverage2);
System.out.println(beverage2.getDescription()
+ " $" + beverage2.cost());
/// Espresso $1.99
/// Dark Roast Coffee, Mocha, Mocha, Whip $1.49
我们直接从测试代码开始看,揣摩一下装饰者模式运行的机制。首先第一小段,是我们熟悉的多态,虽然调用的是beverage.getDescription(),但是由于动态绑定(多态),最终调用了Espresso的desciption,cost也是一样的。接下来那一段就有点奇怪了,首先new了一个DarkRoast,然后将实例beverage2放入Mocha的构造器中,然后又“包”了几层,最终调用的结果是怎么出来的?带着这个疑问,我Debug了一下:
看到这里就很清晰了,当调用berverage2.getDescription()的时候,这个调用递归地“传导”到最里层DarkRoast
public DarkRoast() {
description = "Dark Roast Coffee";
}
调用了其构造器,然后打印出 Dark Roast Coffee(继承了berverage的getDescription方法),紧接着调用了Mocha的getDecription方法
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
以此类推…最终打印出Dark Roast Coffee, Mocha, Mocha, Whip 后面那个价格也是类似的。
文章的最后,我们来回顾一下文章开始的那两个问题,装饰者模式就是通过构造器组合的方式,一层层地“动态地”把责任附加在concreteComponent上,最终让concreteComponent得到了“装饰”,然后装饰者模式这种利用了多态+组合这种方式模拟了继承的功能,但是又没有继承那种会让子类强行拥有其特性的脆弱性,所以说这种模式是很好的替代方案。