22.2 解 决 方 案
22.2.1 使用装饰模式来解决问题
用来解决上述问题的一个合理的解决方案,就是使用装饰模式。那么什么是装饰模式呢?
1.装饰模式的定义
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活
2.应用装饰模式来解决问题的思路
虽然经过简化,业务简单了很多,但是需要解决的问题仍不少,还需要解决:透明地给一个对象增加功能,并实现功能的动态组合。
所谓透明地给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。而实现了给一个对象透明地增加功能,自然就实现了功能的动态组合,比如,原来的对象有A功能,现在透明地给它增加了一个B功能,是不是就相当于动态组合了A和B功能呢。
要想实现透明地给一个对象增加功能,也就是要扩展对象的功能,使用继承啊!有人马上提出了一个方案,但很快就被否决了,如果要减少或者修改功能呢?事实上继承是非常不灵活的复用方式。那就用“对象组合”嘛!又有人提出新的方案来了,这个方案得到了大家的赞同。
在装饰模式的实现中,为了能够实现和原来使用被装饰对象的代码无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类中,转调被装饰的对象,在转调的前后添加新的功能,这就实现了给被装饰对象增加功能,这个思路和“对象组合”非常相似。如果对“对象组合”不熟悉,请参见22.3.1的第2小节。
在转调的时候,如果觉得被装饰对象的功能不再需要了,还可以直接替换掉,也就是不再转调,而是在装饰对象中完成全新的实现。
22.2.2 装饰模式的结构和说明
装饰模式的结构如图22.1所示。
图22.1 装饰模式的结构图
■ Component:组件对象的接口,可以给这些对象动态地添加职责。
■ ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责。
■ Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个Component对象,其实就是持有一个被装饰的对象。
注意
注意这个被装饰的对象不一定是最原始的那个对象了,也可能是被其他装饰器装饰过后的对象,反正都是实现的同一个接口,也就是同一类型。
■ ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。
22.2.3 装饰模式示例代码
(1)先来看看组件对象的接口定义。示例代码如下:
(2)定义了接口,那就看看具体实现组件对象的示意。示例代码如下:
(3)接下来看看抽象的装饰器对象。示例代码如下:
(4)该来看看具体的装饰器实现对象了。这里有两个示意对象,一个示意了添加状态,一个示意了添加职责。
先看看添加了状态的示意对象吧。示例代码如下:
接下来看看添加职责的示意对象,示例代码如下:
22.2.4 使用装饰模式重写示例
看完了装饰模式的基本知识,接下来考虑如何使用装饰模式重写前面的示例了。要使用装饰模式来重写前面的示例,大致会有以下改变。
■ 需要定义一个组件对象的接口,在这个接口中定义计算奖金的业务方法,因为外部就是使用这个接口来操作装饰模式构成的对象结构中的对象。
■ 需要添加一个基本的实现组件接口的对象,可以让它返回奖金为0就可以了。
■ 把各个计算奖金的规则当作装饰器对象,需要为它们定义一个统一的抽象的装饰器对象,方便约束各个具体的装饰器的接口。
■ 把各个计算奖金的规则实现成为具体的装饰器对象。
下面看看现在示例的整体结构,以便整体理解和把握示例,如图22.2所示。
(1)计算奖金的组件接口和基本的实现对象。
在计算奖金的组件接口中,需要定义原本的业务方法,也就是实现奖金计算的方法。示例代码如下:
图22.2 使用装饰模式重写示例的程序结构示意图
为这个业务接口提供一个基本的实现。示例代码如下:
(2)定义抽象的装饰器。
在进一步定义装饰器之前,先定义出各个装饰器公共的父类,在这里定义所有装饰器对象需要实现的方法。这个父类应该实现组件的接口,这样才能保证装饰后的对象仍然可以继续被装饰。示例代码如下:
(3)定义一系列的装饰器对象。
用一个具体的装饰器对象,来实现一条计算奖金的规则。现在有三条计算奖金的规则,那就对应有三个装饰器对象来实现。下面依次来看看它们的实现。
这些装饰器涉及到的TempDB和以前一样,这里就不再赘述。
首先来看看实现计算当月业务奖金的装饰器。示例代码如下:
接下来看看实现计算累计奖金的装饰器。示例代码如下:
接下来看看实现计算当月团队业务奖金的装饰器。示例代码如下:
(4)使用装饰器的客户端。
使用装饰器的客户端,首先需要创建被装饰的对象,然后创建需要的装饰对象,接下来重要的工作就是组合装饰对象,依次对前面的对象进行装饰。
有很多类似的例子,比如生活中的装修,就拿装饰墙壁来说吧,没有装饰前是原始的砖墙,这就好比是被装饰的对象,首先需要刷腻子,把墙找平,这就好比对原始的砖墙进行了一次装饰,而刷的腻子就好比是一个装饰器对象;好了,装饰一回了,接下来该刷墙面漆了,这又好比装饰了一回,刷的墙面漆就好比是又一个装饰器对象,而且这回被装饰的对象不是原始的砖墙了,而是被腻子装饰器装饰过后的墙面,也就是说后面的装饰器是在前面的装饰器装饰过后的基础之上,继续装饰的,类似于一层一层叠加的功能。
同样的道理,计算奖金也是这样。先创建基本的奖金对象,然后组合需要计算的奖金类型,依次组合计算,最后的结果就是总的奖金。示例代码如下:
测试一下,看看结果。示例如下:
当测试运行的时候会按照装饰器的组合顺序,依次调用相应的装饰器来执行业务功能,是一个递归的调用方法,以业务经理“王五”的奖金计算做例子,画个图来说明奖金的计算过程吧,看看是如何调用的,如图22.3所示。
图22.3 装饰模式示例的组合和调用过程示意图
提示
这个图很好地揭示了装饰模式的组合和调用过程,请仔细体会一下。
如同上面的示例,对于基本的计算奖金的对象而言,由于计算奖金的逻辑太过于复杂,而且需要在不同的情况下进行不同的运算,为了灵活性,把多种计算奖金的方式分散到不同的装饰器对象中,采用动态组合的方式,来给基本的计算奖金的对象增添计算奖金的功能,每个装饰器相当于计算奖金的一个部分。
这种方式明显比为基本的计算奖金的对象增加子类更灵活,因为装饰模式的起源点是采用对象组合的方式,然后在组合的时候顺便增加些功能。为了达到一层一层组装的效果,装饰模式还要求装饰器要实现与被装饰对象相同的业务接口,这样才能以同一种方式依次组合下去。
灵活性还体现在动态上,如果是继承的方式,那么所有的类实例都有这个功能了,而采用装饰模式,可以动态地为某几个对象实例添加功能,而不是对整个类添加功能。比如上面示例中,客户端测试的时候,对张三、李四就只组合了两个功能,对王五就组合了三个功能,但是原始的计算奖金的类都是一样的,只是动态地为它增加的功能不同而已。