什么是组合模式?它解决什么问题?
简化版
组合模式是把对象组织成树形结构,让客户端可以用一致的方式处理单个对象和组合对象。它主要解决树形层级结构中“叶子节点”和“容器节点”处理方式不统一的问题。
详细版
组合模式的核心思想是:叶子对象和组合对象实现同一个抽象接口。
例如文件系统:
目录
├── 文件 A
├── 文件 B
└── 子目录
└── 文件 C
文件是叶子节点,目录是组合节点。但客户端希望统一调用:
node.getSize();
node.print();
不想每次判断:
if (node is File) ...
if (node is Directory) ...
组合模式适合组织架构、菜单树、文件目录、权限树、商品分类、UI 组件树等天然树形结构。
完整版教学
一、为什么树形结构容易写乱
很多业务都有树形结构:
- 文件和目录;
- 公司部门和员工;
- 菜单和菜单项;
- 商品分类;
- 权限资源树;
- UI 组件树;
- 表达式语法树。
如果每一层都用不同接口,客户端就要写大量判断。
组合模式通过统一抽象,让客户端面对的是同一种组件。
二、组合模式的关键抽象
组合模式通常会定义一个公共组件接口:
interface Component {
void operation();
}
叶子节点实现它:
class Leaf implements Component {
public void operation() {
// 叶子自己的操作
}
}
组合节点也实现它:
class Composite implements Component {
private List<Component> children;
public void operation() {
for (Component child : children) {
child.operation();
}
}
}
这样组合节点可以递归调用子节点。
三、组合模式解决的是一致处理
组合模式最重要的价值不是“把对象放到树里”,而是让客户端一致处理整棵树。
例如:
Component root = buildTree();
root.operation();
客户端不关心 root 下面是文件、目录、子目录还是更深层结构。
四、组合模式天然适合递归
树结构通常有递归特征:
一个目录里可以有文件,也可以有目录
一个部门里可以有员工,也可以有子部门
一个菜单里可以有菜单项,也可以有子菜单
组合模式把这种递归关系映射到对象结构里。
组合对象持有子组件,子组件又可能是组合对象。
五、组合模式不是所有层级都要用
如果结构只有两层,而且业务很简单,直接写普通对象即可。
组合模式适合层级不固定、节点类型需要统一处理、递归操作较多的场景。
如果层级固定且逻辑简单,强行套组合模式会增加抽象成本。
六、常见误区与追问
组合模式把单个对象和对象集合放在同一抽象下。根目录含2个文件和1个子目录时,客户端只调用根的 size(),叶子返回自身大小,组合节点递归求和。它解决的是部分—整体的一致使用;若客户端仍到处 instanceof 判断节点类型,核心收益尚未实现。
| 检查维度 | 判定依据 |
|---|---|
| Leaf | 表示不可再分的部分 |
| Composite | 表示整体并保存 Component 子节点 |
记忆钩子:叶子是“一个”,组合是“一组”,客户端用同一个 Component 看待二者。
- 误区:组合模式只是普通的树数据结构。 它还要求叶子与容器共享抽象,使客户端能一致调用。
- 追问:为什么它属于结构型模式? 它关注对象如何递归组合成部分—整体结构。
- 误区:所有节点必须拥有完全相同的能力。 核心业务操作需统一,子节点管理能力可以采用安全式分离。
- 追问:组合操作如何返回结果? 可对子节点求和、拼接或聚合,具体规则由业务语义决定。
- 追问:什么时候会退化成类型判断? 公共抽象过弱,导致客户端必须识别具体 Leaf 或 Composite 才能工作。
七、加强记忆
组合模式的核心是“树形结构,一致处理”。叶子和容器实现同一个组件接口,客户端不用关心对象是单个节点还是组合节点,直接按统一方式操作整棵树。