使用组合模式有哪些常见误区?
简化版
组合模式常见误区包括:没有树形结构也硬套、叶子节点暴露不该有的方法、忽略递归深度和性能、没有处理环形引用、把数据库树模型等同于组合模式、在组合节点里堆过多业务逻辑。组合模式适合部分-整体树,不适合所有包含关系。
详细版
常见误区:
- 普通一对多关系也强行套组合模式;
- 透明式接口让叶子节点暴露
add/remove却没有合理处理; - 递归遍历大树导致性能问题;
- 没有做环检测,异常数据导致死循环;
- 删除或移动节点时父子关系不一致;
- 把数据库 parent_id 结构直接等同于组合模式;
- Composite 类承担过多业务规则;
- 对所有节点强行使用同一个接口,导致抽象过宽;
- 忽略权限过滤后的树结构修剪;
- 没有区分构建树和使用树两个阶段。
正确使用组合模式,要先确认业务是否真的是递归树,并明确叶子和组合节点的共同能力。
完整版教学
一、误区一:没有树也硬套
组合模式适合递归树。
如果只是:
订单 -> 订单明细
这通常是一层聚合关系,不一定需要组合模式。
组合模式更适合:
节点 -> 子节点 -> 子节点 -> ...
层级不固定,结构递归,才更有价值。
二、误区二:接口设计过宽
如果 Component 接口里放太多方法:
add()
remove()
getChild()
calculate()
render()
export()
很多叶子节点可能无法合理实现。
接口应该只放真正公共的能力,子节点管理方法要根据透明式或安全式慎重设计。
三、误区三:忽略环形引用
业务上说是树,不代表数据一定干净。
错误数据可能形成环:
A -> B -> C -> A
递归遍历会无限循环。
构建树时要校验父子关系,遍历时必要情况下维护 visited 集合。
四、误区四:把数据库树模型当成组合模式
数据库里用 parent_id 存树,只是存储模型。
组合模式是内存对象设计:
Component
Leaf
Composite
两者可以配合,但不是一回事。
通常流程是:从数据库查节点列表,再组装成组合对象树。
五、误区五:组合节点变成大业务类
Composite 的主要职责是管理子节点并实现组合行为。
如果把各种业务规则都塞进去,它会变得很重。
例如菜单节点不应该承担所有权限系统逻辑,权限判断可以放到权限服务或策略中。
六、误区六:构建树和使用树混在一起
构建树时可能需要:
- 查数据库;
- 建索引;
- 挂父子关系;
- 排序;
- 校验环。
使用树时可能只是:
- 渲染;
- 统计;
- 过滤;
- 遍历。
这两个阶段职责不同,不要全部混在 Component 里。
七、常见误区与追问
组合模式误用通常来自“看见层级就套树接口”。如果节点只有2层且叶子、容器操作完全不同,统一 Component 反而迫使叶子实现无意义的 add/remove。真正匹配的场景需要客户端对叶子和组合执行同一业务操作,并接受递归传播语义。
| 检查维度 | 判定依据 |
|---|---|
| 适合统一 | 叶子与容器共享核心业务操作 |
| 不宜统一 | 两类节点行为差异远大于共性 |
心法:组合模式统一的是“使用方式”,不是把所有节点方法强行做成一样。
- 误区:任何数据库 parentId 都是组合模式。 数据呈树形不等于对象协作采用统一 Component 接口。
- 追问:如何避免叶子暴露无效方法? 选择安全式接口,或把子节点管理能力拆成单独抽象。
- 误区:递归结构天然不会有环。 错误装配可能让祖先成为后代,必须校验或记录访问集合。
- 追问:组合节点为何容易变成大类? 把加载、权限、统计和持久化都塞入节点会混淆结构与业务。
- 追问:构树和遍历要分开吗? 通常应分开,构建负责数据一致性,节点只维护结构和统一操作。
八、加强记忆
组合模式别乱用:它服务于递归树,而不是所有包含关系。使用时要管好接口宽度、树构建、环检测、递归性能和职责边界,别让 Component 抽象过宽,也别让 Composite 变成大业务类。