组合模式是什么
让单个对象与组合对象拥有同一套接口:调用方不必区分叶子与分支,递归结构(目录树、组织架构、界面容器、报表分组)可以用同一段代码处理。代价是接口必须覆盖两类对象——叶子被迫实现它用不到的增删子节点方法,这是个需要正视的取舍。
基本信息
| 意图 | 树形结构的一致访问 |
|---|---|
| 角色 | Component(统一接口)/ Leaf / Composite |
| 场景 | 目录树、权限树、菜单、报表分组、表达式树 |
| 变体 | 透明式(接口含增删)与安全式(只在 Composite 上暴露增删) |
典型使用场景
- 文件系统:文件与目录都提供 size 与遍历能力
- 组织架构:部门与小组都能返回人数与预算合计
- 界面容器:面板与控件都能响应渲染与布局
- 权限树:单个权限与权限组都能判定是否包含某权限
- 报表分组:明细行与分组行都能求和
- 表达式树:常量与运算符节点都能求值
Java 示例
Java 示例
interface Node { long size(); }
class File implements Node {
private final long bytes;
File(long bytes) { this.bytes = bytes; }
@Override public long size() { return bytes; }
}
class Dir implements Node {
private final List<Node> children = new ArrayList<>();
void add(Node child) { children.add(child); }
@Override public long size() {
return children.stream().mapToLong(Node::size).sum(); // 递归聚合
}
}接口设计取舍
| 方式 | 做法 | 代价 |
|---|---|---|
| 透明式 | 增删方法放在统一接口上 | 叶子必须实现或抛出不支持异常 |
| 安全式 | 增删只在 Composite 上暴露 | 调用方需要类型判断或转换 |
| 混合式 | 统一接口加抽象的中间基类 | 继承层次变深,需谨慎设计 |
递归遍历要有深度保护:用户可构造任意深度的结构时,退化链会引发栈溢出。改用显式栈的迭代遍历,或对输入深度设上限并在超限时明确报错。
结构被频繁遍历时要考虑缓存:每次查询都递归求和,在大树上代价明显。可缓存聚合结果并在结构变更时失效——注意失效必须覆盖所有变更入口,否则会返回过期数据。