1. 概述
组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。
这种设计模式也蛮简单的,说一种设计模式简单,怎么看的呢?看它的应用场景,应用场景很清晰就很简单,因为具体实现其实可以说是死代码,只要记住哪种场景可以使用哪种模式即可,代码实现倒是不必死记硬背,明确使用场景倒是首要的。
组合模式的使用场景很清楚,它适用于多个对象之间的关系是树状的情况(程序员应该不会不知道树的吧),这种场景其实也挺常见的,比如我们的学校架构,首先根节点是学校,然后接下来是学院,每个学院又有每个学院的专业,专业下面又有班,这就构成了一种树状关系。
其实有一说一,这种情况常见是常见,但我们一般不会在业务中(JavaWeb)处理这些关系,一般都是交给了数据库层(一对一、一对多)处理的。但是现在假如我们遇到了该怎么办呢?应该如何处理呢?
从上面这个学校架构的例子下手,首先最容易想到也最不实用的方法就是使用继承,即学校作为最高的父类,学院类继承学校类,专业类继承学院类,班级类继承专业类,为什么说这个方法不实用呢?
因为它使用的是继承,使用继承在逻辑上也说不过去,且无法解决一个很核心的问题:众所周知,在树中一个很核心的问题就是遍历,现在使用继承,那怎么遍历这个类构成的树呢?比如说怎么从一个学校找到它里面有什么学院?从一个学院怎么找到它有什么专业?很明显使用继承无法解决这个问题。
那该怎么做呢?其实答案也不难,使用组合,我在除了叶子节点(这个例子里面是班级类),其他节点里面都设置一个List
集合来存储下一层的节点,比如学校类里面使用一个List
存储学院类,学院类里面用List
存储专业类。这样做可以了吗?
可以,组合模式就是使用的聚合,但是也不仅仅是这样,组合模式还加入了一个位于顶层的抽象类或者接口,这样做有什么好处呢?
好处就是可以定义所有类共有的一些方法,或者部分类共有的方法,这样客户端不必了解所有类,因为他们都实现或继承了同样的接口,在客户端眼里它们都是相同的,客户端只要了解接口或抽象类怎么使用即可,这样就达到了便于管理的目的。当然,如果某些类有自己的独特操作,客户端也是需要了解一下的。
其实如果定义了抽象类的话,可能就会产生单继承的限制,但总体使用比定义接口方便(个人感觉),因为可以写属性、空方法、抽象方法等。
当然抽象出一个顶层的抽象类或者接口,这个可能也是它的缺点所在,当一些类的差别比较大时,就会比较难抽象出一个抽象类或者接口,又或者抽象出来的抽象类或接口逻辑比较古怪难理解。具体还是看下面的代码实现。
- 总结
- 组合模式适用于解决树状的类关系情况,使用组合,而不是继承,关联起了一个树状的类的关系图
- 组合模式抽象出来了一个位于顶层的抽象类或者接口,定义了所有类共有的特性,也降低客户端的使用难度。
2. 特点
-
优点
- 你可以利用多态和递归机制更方便地使用复杂树结构。
- 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。
-
缺点
- 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。
-
使用场景
-
如果你需要实现树状对象结构, 可以使用组合模式。
组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。
-
如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。
组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。
-
3. 实现
-
UML类图
-
角色说明
- 顶层的抽象类或接口:定义了所有子类或实现类共有的功能,推荐使用抽象类,因为可以有成员变量,也可以定义所有子类都有的方法(抽象方法),也可以定义部分子类有的方法(空方法)。
- 非叶子类:指例子中的学校、学院,非叶子类要组合一个抽象类集合用于表示子节点
- 叶子类:指例子中的班级,这个位于最底层,没有子节点,无需组合一个抽象类集合
- 客户端:调用组合模式的角色
-
Java实现
- 顶层的抽象类或接口
/** * @Author: chy * @Description: 抽象类,含有每个类都有的功能 * @Date: Create in 20:23 2021/3/11 */ public abstract class Deportment { /** * 部门名字 */ protected String name; public Deportment(String name) { this
- 顶层的抽象类或接口