装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

以下是目前的订单系统的类图:

1. 购买咖啡,要求在其中加入各种调料,例如都将、摩卡以及奶泡等等。

我们将所有的种类的咖啡的类图加入之前的结构中去:

很明显,上面造成维护噩梦,如果牛奶价钱上扬或者新增一种焦糖调料风味时,怎么办?需要修改很多代码!

2. 我们尝试修改基类试试

父类cost用于计算调料的价钱,子类cost用饮料的价钱+父类的cost,接下来加入子类

问题:1. 调料价钱或者增加调料类型的改变会改变cost、has和set方法;2. 以后开发出新饮料,某些调料并不适合;3. 想要双倍摩卡咖啡,怎么计算价格。

设计原则:类应该对扩展开放,对修改关闭(上面的基类明显会导致大量的修改)

我们的目标是允许类容易扩展,在不修改类的现有代码的情况下。就可搭配新的行为。如果能实现这样的目标,系统将具有弹性,可以应付改变,可以接受新的功能来应对改变的需求。

3. 认识装饰者模式

首先我们已经意识到继承无法解决问题,修改基类也无法解决问题。所以,在这里要采用不一样的做法:我们要以饮料为主体,然后运行时以调料来“装饰”饮料。比方说,如果顾客想要摩卡和奶泡深焙咖啡,那么,要做的是:

3.1 拿一个深焙咖啡对象;

3.2 以摩卡对象装饰它;

3.3 以奶泡对象装饰它;

3.4 调用cost()方法,并依赖委托将调料的价格加上去。

注意:装饰说白了就是一层一层包装的意思,下面显示我们一层一层包装的类图:

我们总结一下:

装饰者和被装饰对象有相同的超类型。

可以用一个或多个装饰者包装一个对象。

既然装饰者和被装饰者有相同的超类型,所以在任何需要原始对象的场合,可以用装饰过的对象代替它。

核心目的:装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。

对象可以在任何时候被装饰,所以可以在运行时动态地,不限量的用你喜欢的装饰者来装饰对象。

由此,上面加粗处的问题,就迎刃而解了。

4. 定义装饰者模式

继承层次:Component->Decorator->ConcreteDecoratorA/ConcreteDecoratorB。

5. 装饰我们的饮料

实现getDescription()方法:所加的饮料+beverage.getDescription(),这样的话无论包括了多少层,都能得到详细解释、

注意:在策略模式中,我们提到了一个设计原则:多用组合,少用继承。但是这里的装饰者模式,为什么都是用继承来解决问题呢?

答案:

1. 用继承的原因是我们利用继承来达到装饰者和被装饰者之间的“类型匹配”,而不是利用继承获得“行为”;

2. 当我们将装饰者和被装饰者组合时,就是在加入新的行为。所得到的新行为,并不是继承而来,而是组合对象得来的。

6. 代码实现:

Beverage基类

调料基类

饮料类

调料类

几个要点:1.如果我们要对某饮料进行打折,需要修改该饮料类,那么饮料类就在抽象父类的基础上新增了方法,这样的话装饰者模式就不适用了,因为装饰者模式实质上是针对接口编程;

2. 装饰者理论上是无法窥视装饰者链中其他的装饰者的,但并不是做不到,我们可以在外面写一个装饰者CondimentPrettyPrint解析出每一种方法执行的结果,就可以窥视到里面其他的装饰者。

7. 我们看一下java标准库里面的实现

7.1 真实世界里的装饰者:Java I/O

7.2 具体的架构

我们可以看到,和之前的例子的设计没有太大的差异。

装饰者模式缺点:常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。

8. 让我们简单写自己的I/O装饰者

我们总结一下所有的要点:

1. 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案;

2. 在我们的设计中,应该允许行为可以被扩展,而无须修改现有的代码;

3. 组合和委托可以用于运行时动态的加入新的行为;

4. 除了继承,装饰者模式也可以让我们扩展行为;

5. 装饰者模式意味着一群装饰者类,这些类用来包装具体组件;

6. 装饰者类和组件有相同的类型;

7. 装饰者可以在被装饰者的行为前面或者后面加上自己的行为,甚至将被装饰者行为整个取代,而达到特定的目的;

8. 可以用无数个装饰者包装一个组件;

9. 装饰者一般对组件的客户是透明的,除非客户程序依赖组件的具体类型;

10. 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。