本文目录导读:

目录导读
- 组合模式核心概念 – 什么是结构型设计模式中的“树形递归”
- UML结构解析 – 透明式与安全式的取舍
- 需求场景模拟 – 文件系统/组织架构/菜单权限的通用建模
- Java代码精讲 – 抽象组件、叶子节点、容器节点的落地实现
- 业务案例:电商后台的类目树 – 结合数据库与缓存的全链路设计
- 组合模式与递归遍历的陷阱 – 常见坑点与性能调优
- 高频面试问答 – 组合模式与装饰器/迭代器的区别
组合模式核心概念
组合模式(Composite Pattern)允许你将对象组合成树形结构以表达“部分-整体”的层次关系,它让客户端能够以统一方式处理单个对象(叶子节点)和组合对象(容器节点),这打破了“类型判断”的耦合。
核心价值:客户端代码无需关心操作的是单个文件还是整个文件夹,其调用行为完全一致。
与继承的区别
传统继承的树形结构是静态的,而组合模式是基于对象引用的动态组合,一个Category既可以包含子Category,也可以包含Product,这种关系在运行时可以自由调整。
UML结构解析(透明式 vs 安全式)
| 模式风格 | 定义位置 | 优点 | 缺点 |
|---|---|---|---|
| 透明式 | 抽象类声明所有操作(add/remove/getChild) | 叶子与容器结构完全一致 | 叶子节点空实现易产生异常 |
| 安全式 | 容器节点独有add/remove方法 | 接口设计更严谨 | 客户端需用instanceof判断类型 |
实战推荐:大多数企业级系统(如商品分类)使用安全式,因为叶子节点(商品)的增删操作根本没意义。
需求场景模拟
- 文件系统:文件夹中包含子文件夹和文件
- 组织架构:领导下属中存在不同层级的员工
- 菜单权限:一级菜单包含二级菜单及按钮权限
- 电商类目:一级类目 → 二级类目 → 品牌 → 具体商品SPU
这些场景的共性:数据呈递归嵌套结构,且遍历方式一致。
Java代码精讲
// 抽象组件(安全式)
public abstract class Component {
protected String name;
public Component(String name) { this.name = name; }
public abstract void operation(); // 统一业务方法
}
// 叶子节点(如商品)
public class Leaf extends Component {
public Leaf(String name) { super(name); }
@Override
public void operation() { System.out.println("Leaf: " + name); }
}
// 容器节点(如类目)
public class Composite extends Component {
private List<Component> children = new ArrayList<>();
public Composite(String name) { super(name); }
public void add(Component comp) { children.add(comp); }
public void remove(Component comp) { children.remove(comp); }
@Override
public void operation() {
System.out.println("Composite: " + name);
children.forEach(Component::operation); // 递归调用
}
}
关键设计要点
- 递归遍历:容器的
operation()内部必须循环调用子节点的同名方法。 - 深拷贝保护:如果子节点需要拷贝,建议重写
clone()配合深度遍历。 - 透明访问:容器节点对外暴露
getChildren()便于进行迭代。
业务案例:电商后台的类目树
需求:管理一个支持无限级子类目的树形结构,每个类目下可挂具体商品。
数据库设计
CREATE TABLE category ( id INT PRIMARY KEY, parent_id INT DEFAULT 0, name VARCHAR(50) );
代码整合(简化版)
// 加载数据为树结构
public Composite buildTree(List<Category> list) {
Map<Integer, Composite> map = new HashMap<>();
Composite root = null;
for (Category c : list) {
Composite comp = new Composite(c.getName());
map.put(c.getId(), comp);
if (c.getParentId() == 0) root = comp;
}
// 二次循环建立关联
for (Category c : list) {
if (c.getParentId() != 0) {
map.get(c.getParentId()).add(map.get(c.getId()));
}
}
return root;
}
性能优化:在百万级类目数据中,避免递归查询数据库,采用一次性查询全部再在内存中组装为树,最后存入Redis缓存。
组合模式与递归遍历的陷阱
- 循环引用:子节点指向父节点会导致死循环,解决方案:使用
Set记录访问过的节点身份ID。 - 深层次递归栈溢出:若层级超过1000层,可改用非递归栈遍历(DFS),在企业应用中,建议控制最大层级不超过10。
- 频繁删除/移动:需保证父子引用同步更新,避免内存中残留孤儿节点。
高频面试问答
Q1:组合模式和装饰器模式的区别?
装饰器是一条链,强调动态添加职责;组合模式是一棵树,强调部分与整体的层次关系,IO流中使用装饰器包装
BufferedReader,而菜单渲染使用组合模式。
Q2:什么时候必须用组合模式?
当你的业务遍历逻辑对所有节点完全一致,且节点层级动态变化(如权限树、部门树)时,组合模式可以消除大量
if-else类型判断。
Q3:组合模式会造成系统过度设计吗?
如果业务中只有一层嵌套(如列表+详情),无需使用,判断标准:是否反复出现“递归遍历”且节点行为类似。
Java组合案例的精髓在于用同一套接口描述个体与群体,在Eclipse RCP、SWT界面库、以及Spring中的CompositeCacheManager都能看到它的影子,掌握其代码结构与递归陷阱,你就能在复杂业务树形建模中游刃有余。
延伸思考:如果你需要在组合模式上增加动态权限过滤,你会选择在Composite节点添加过滤逻辑,还是使用职责链模式?欢迎在评论区分你见解。