15.2 解 决 方 案
15.2.1 使用组合模式来解决问题
用来解决上述问题的一个合理的解决方案就是组合模式。那么什么是组合模式呢?
1.组合模式的定义
将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
2.应用组合模式来解决问题的思路
仔细分析上面不用模式的例子,要区分组合对象和叶子对象的根本原因,就在于没有把组合对象和叶子对象统一起来。也就是说,组合对象类型和叶子对象类型是完全不同的类型,这导致了操作的时候必须区分它们。
组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是叶子对象。
提示
组合模式的关键就在于这个抽象类,这个抽象类既可以代表叶子对象,也可以代表组合对象,这样用户在操作的时候,对单个对象和组合对象的使用就具有了一致性。
15.2.2 组合模式的结构和说明
组合模式的结构如图15.1所示。
图15.1 组合模式结构示意图
■ Component:抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
■ Leaf:叶子节点对象,定义和实现叶子对象的行为,不再包含其他的子节点对象。
■ Composite:组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作。
■ Client:客户端,通过组件接口来操作组合结构里面的组件对象。
一种典型的Composite对象结构通常是如图15.2所示的树型结构,一个Composite对象可以包含多个叶子对象和其他的Composite对象。虽然图15.2看起来好像有些对称,但那只是为了让图看起来美观一点,并不是说Composite组合的对象结构就是这样对称的,这点要提前说明一下。
15.2.3 组合模式示例代码
(1)先来看看组件对象的定义。示例代码如下:
(2)接下来看看Composite对象的定义。示例代码如下:
(3)该来看看叶子对象的定义了。相对而言比较简单。示例代码如下:
(4)对于Client,就是使用Component接口来操作组合对象结构,由于使用方式千差万别,这里仅仅提供一个示范性质的使用,顺便当作测试代码使用。示例代码如下:
15.2.4 使用组合模式重写示例
理解了组合模式的定义、结构和示例代码,对组合模式应该有一定的掌握了吧。下面就使用组合模式来重写前面不用模式的示例,看看用组合模式来实现会是什么样子,和不用模式有什么相同和不同之处。
为了整体理解和把握整个示例,先来看看示例的整体结构,如图15.3所示。
图15.3 使用组合模式实现示例的结构示意图
(1)为组合对象和叶子对象添加一个抽象的父对象做为组件对象。在组件对象中,定义一个输出组件本身名称的方法以实现要求的功能。示例代码如下:
(2)来看看叶子对象的实现,它的变化比较少,只是让叶子对象继承了组件对象,其他的和不用模式相比,没有什么变化。
示例代码如下:
(3)接下来看看组合对象的实现,这个对象变化就比较多,大致有如下的改变。
■ 新的Composite对象需要继承组件对象。
■ 原来用来记录包含其他组合对象的集合和包含其他叶子对象的集合,被合并成为一个,就是统一的包含其他子组件对象的集合。使用组合模式来实现,不再需要区分到底是组合对象还是叶子对象了。
■ 原来的addComposite和addLeaf方法,可以不需要了,将其合并实现成组件对象中定义的addChild方法,但是需要现在的Composite来实现这个方法。使用组合模式来实现,不再需要区分到底是组合对象还是叶子对象了。
■ 原来的printStruct方法的实现,完全要按照现在的方式来写,变化较大。
具体的示例代码如下:
(4)客户端也有变化。客户端不再需要区分组合对象和叶子对象了,统一使用组件对象,调用的方法也都要改变成组件对象定义的方法。示例代码如下:
从上面的示例,大家可以看出,通过使用组合模式,把一个“部分—整体”的层次结构表示成了对象树的结构。这样一来,客户端就无需再区分操作的是组合对象还是叶子对象了;对于客户端而言,操作的都是组件对象。