Java 23 种设计模式:从踩坑到精通 | 组合模式 —— 树形结构处理,部分与整体一视同仁
Java 23 种设计模式:从踩坑到精通 | 组合模式 —— 树形结构处理,部分与整体一视同仁
摘要:业务代码里充斥着
if-else来判断是“文件”还是“文件夹”?每次新增一种节点类型都要修改十几处逻辑?在 2026 年的微服务架构中,这种“面条代码”往往是性能下降的隐形杀手。本文带你用组合模式彻底消灭这些重复判断——通过透明式与安全式的深度剖析,结合 Spring Security 源码实录与 AI 辅助编程演示,让你彻底掌握树形结构处理的终极奥义。
📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:桥接模式 | 当前:组合模式 | 下一篇:装饰器模式
🔗 返回系列总目录
1. 你还在写“面条代码”吗?
假设你正在开发一个 2026 年的低代码平台,需要处理复杂的页面组件树:
if (component.isContainer()) {
for (Component child : component.getChildren()) {
// 递归处理
}
} else {
// 渲染组件
}
当产品说“我们要增加一种循环容器”时,你必须去改所有写过 if-else 的地方。这不仅违背了开闭原则,更让代码变得像面条一样纠缠不清。在微服务架构中,复杂的树形菜单或权限结构处理往往成为服务性能的隐形杀手。
组合模式(Composite Pattern)正是为解决这种“部分-整体层次结构”的访问一致性而生的:它将对象组合成树形结构以表示“部分-整体”的层次关系,使得客户端对单个对象和组合对象的使用具有一致性。
💡 核心逻辑:组合模式 = 树形结构 + 一致接口。
1.1 你的场景该不该用组合模式?
| 判断标准 | 是 → 用组合模式 | 否 → 用其他方式 |
|---|---|---|
| 对象之间存在明显的“整体-部分”层次关系 | ✅ | ❌ |
| 需要统一处理单个对象和组合对象 | ✅ | ❌ |
| 希望新增节点类型不影响客户端代码 | ✅ | ❌ |
| 结构扁平,无需递归处理 | ❌ | 直接集合遍历即可 |
2. 模式定义与 UML 结构
组合模式 将对象组合成树形结构以表示“部分-整体”的层次关系,使得用户对单个对象和组合对象的使用具有一致性。它属于 结构型设计模式。
标准组合模式的 UML 类图:

三个角色:
- Component(抽象构件):定义叶子构件和容器构件的公共接口,可以是抽象类或接口;
- Leaf(叶子构件):表示树中的叶子节点,没有子节点;
- Composite(容器构件):包含子节点(可以是叶子或其他容器),提供管理子节点的方法和统一的
operation()。
💡 根据管理子节点方法(
add/remove)的位置,可分为透明式组合和安全式组合两种。
3. 透明组合与安全组合
3.1 透明组合
将所有管理方法(add/remove)定义在抽象 Component 中,让 Leaf 和 Composite 都实现它们。Leaf 的方法抛出异常或不处理。这样客户端完全一致,但运行时可能抛出异常。
3.2 安全组合
只在 Composite 中定义管理方法,Component 中只定义业务方法。这样更安全,不会在 Leaf 上调用无意义的方法,但客户端必须区分 Leaf 和 Composite,牺牲了部分透明性。
⚠️ 避坑指南:在实际项目中,除非你有极强的统一接口需求,否则强烈建议使用安全组合。否则后期维护时,
Leaf节点上不断抛出的UnsupportedOperationException会让你头疼不已。安全组合更符合单一职责原则。
4. 代码实现:文件系统(透明组合)
4.1 抽象构件
public abstract class FileSystemNode {
protected String name;
public FileSystemNode(String name) {
this.name = name;
}
public abstract void show(int depth);
// 管理子节点方法(透明组合)
public void add(FileSystemNode node) {
throw new UnsupportedOperationException();
}
public void remove(FileSystemNode node) {
throw new UnsupportedOperationException();
}
}
4.2 叶子构件:文件
public class FileNode extends FileSystemNode {
public FileNode(String name) {
super(name);
}
@Override
public void show(int depth) {
// 建议在此处打断点,观察递归过程中 depth 的变化
System.out.println(" ".repeat(depth) + "📄 文件:" + name);
}
}
4.3 容器构件:文件夹
public class FolderNode extends FileSystemNode {
private List<FileSystemNode> children = new ArrayList<>();
public FolderNode(String name) {
super(name);
}
@Override
public void add(FileSystemNode node) {
children.add(node);
}
@Override
public void remove(FileSystemNode node) {
children.remove(node);
}
@Override
public void show(int depth) {
// 建议在 show() 处打断点,观察 children 的递归压栈过程
System.out.println(" ".repeat(depth) + "📁 文件夹:" + name);
for (FileSystemNode child : children) {
child.show(depth + 1); // 递归调用,深度+1
}
}
}
4.4 客户端调用
public class Client {
public static void main(String[] args) {
FileSystemNode root = new FolderNode("root");
FileSystemNode home = new FolderNode("home");
FileSystemNode docs = new FolderNode("docs");
FileSystemNode file1 = new FileNode("readme.txt");
FileSystemNode file2 = new FileNode("image.png");
root.add(home);
root.add(docs);
docs.add(file1);
docs.add(file2);
root.show(0);
}
}
运行结果:
📁 文件夹:root
📁 文件夹:home
📁 文件夹:docs
📄 文件:readme.txt
📄 文件:image.png
客户端完全不用区分文件和文件夹,统一调用 show() 即可递归展示整棵树。
🔧 调试技巧:在
FolderNode.show()处打断点,观察children的递归压栈过程,你会对组合模式的执行流程有更直观的理解。
5. 代码实现:组织架构(安全组合,推荐)
5.1 抽象构件
public interface Employee {
void showDetail();
}
5.2 叶子构件:员工
public class IndividualEmployee implements Employee {
private String name;
private String position;
public IndividualEmployee(String name, String position) {
this.name = name;
this.position = position;
}
@Override
public void showDetail() {
System.out.println(" 员工:" + name + ",职位:" + position);
}
}
5.3 容器构件:部门
public class Department implements Employee {
private String name;
private List<Employee> members = new ArrayList<>();
public Department(String name) {
this.name = name;
}
// 子节点管理方法只在 Composite 中定义(安全组合)
public void add(Employee e) {
members.add(e);
}
public void remove(Employee e) {
members.remove(e);
}
@Override
public void showDetail() {
System.out.println("部门:" + name);
for (Employee member : members) {
member.showDetail();
}
}
}
5.4 客户端调用
Department company = new Department("总公司");
Department devDept = new Department("研发部");
Employee alice = new IndividualEmployee("Alice", "高级工程师");
Employee bob = new IndividualEmployee("Bob", "实习生");
devDept.add(alice);
devDept.add(bob);
company.add(devDept);
company.showDetail();
输出:
部门:总公司
部门:研发部
员工:Alice,职位:高级工程师
员工:Bob,职位:实习生
如果新增一个“子公司”部门,它同样是 Employee 类型,只需将子部门加入总公司,无需修改现有代码。
6. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 一致性:客户端统一处理叶子与容器,简化代码 | 设计复杂度增加:需要引入抽象层,类数量增多 |
| 符合开闭原则:新增叶子或容器类型无需修改现有代码 | 不易限制类型:若需强制只允许特定子类型,需额外约束 |
| 清晰的层次结构:形成树形结构,符合自然认知 | 透明组合下,叶子节点存在无用方法,违背接口隔离原则 |
| 递归操作天然适合:如遍历、统计、查找 |
⚠️ 避坑:如果树深度过大或节点过多,递归遍历可能导致栈溢出或性能瓶颈。此时可考虑懒加载(只加载当前层,展开时再加载子节点)或迭代器模式替代递归。
7. 进阶:Spring Security 源码中的组合模式
Spring Security 的配置构建器是组合模式的经典应用。HttpSecurity 作为容器,SecurityConfigurer 的各种子类作为叶子或容器,通过组合形成安全配置树。
核心源码(AbstractConfiguredSecurityBuilder 简化版):
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
// 配置器列表(组合模式的 children)
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>,
List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
// 添加配置器(组合模式的 add)
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
configurer.init(this); // 初始化
configurer.configure(this); // 配置
return configurer;
}
// 构建时遍历所有配置器(组合模式的递归处理)
@Override
protected final O doBuild() throws Exception {
for (SecurityConfigurer<O, B> configurer : getConfigurers()) {
configurer.configure(this); // 统一调用
}
return performBuild();
}
}
对应关系:
| 组合模式角色 | Spring Security 中的对应 |
|---|---|
| Component | SecurityConfigurer<O, B> |
| Leaf | CorsConfigurer、CsrfConfigurer(只配置单一功能) |
| Composite | HttpSecurity(包含多个子配置器) |
| operation() | configure(HttpSecurity) |
| add() | apply(C configurer) |
客户端调用:
http
.cors().and() // 添加 CorsConfigurer
.csrf().disable() // 修改 CsrfConfigurer
.authorizeRequests()
.anyRequest().authenticated();
💡
HttpSecurity通过and()方法返回父级容器,形成配置链。最终调用build()时,递归遍历所有子配置器,统一应用安全规则——这正是组合模式的“统一处理”思想的体现。
8. 组合模式 vs 解释器模式(面试连环炮)
面试官:“组合模式和解释器模式都用到了树,它们有什么关系?”
答:虽然都用树,但解释器模式通常建立在组合模式的基础上(如 Spring Expression Language 解析表达式时,先构建组合树,再递归求值)。组合模式关注 “结构”(统一处理部分与整体),解释器关注 “计算”(递归求值语法树)。
| 对比维度 | 组合模式 | 解释器模式 |
|---|---|---|
| 目的 | 统一处理部分与整体 | 解释语言句子,求值 |
| 节点行为 | operation() 统一,叶子与容器行为相似 |
interpret() 计算,不同类型节点行为差异大 |
| 典型应用 | 文件系统、组织架构 | 表达式求值、正则 |
| 组合关系 | 独立使用 | 通常建立在组合模式之上 |
9. AI 时代的组合模式
9.1 AI 辅助生成组合模式代码
推荐 Prompt:
“我有一个FileSystemNode抽象类,它有FolderNode容器和FileNode叶子两种实现。请帮我新增一个ShortcutNode叶子节点,它指向另一个FileSystemNode,并在show()方法中显示 ‘快捷方式 -> 目标文件名’。遵循透明组合模式,保持客户端调用一致。”
AI 会快速生成符合组合模式的类结构,无需手动复制粘贴现有代码。你还可以进一步要求:
“请在这段代码中增加
isLeaf()方法,方便客户端判断当前节点是否为叶子,并给出使用示例。”
9.2 常见 AI 交互场景
| 场景 | Prompt 示例 |
|---|---|
| 新增节点类型 | “基于组合模式,新增一个循环容器节点,遍历子节点 N 次” |
| 批量操作 | “给所有叶子节点增加权限编码属性,并在打印时显示” |
| 性能优化 | “如果组合树深度过大,如何用迭代器替代递归遍历?” |
10. 常见误区与面试高频题
❌ 误区1:组合模式就是树结构
组合模式不仅提供树结构,更强调客户端对叶子和容器使用的一致性,这是它区别于普通树的核心。
❌ 误区2:透明组合一定比安全组合好
透明组合牺牲了安全性,增加了运行时错误风险。在多数业务场景中,安全组合更符合单一职责原则。
❌ 误区3:组合模式适用于所有树形场景
如果树深度过大或节点过多,递归遍历可能导致栈溢出。此时应配合懒加载或迭代器模式使用。
💡 面试高频追问
- 组合模式的好处? → 客户端无需区分叶子与容器,统一操作,易于扩展。
- 透明组合与安全组合的区别? → 管理子节点方法的位置不同,前者放在抽象构件,后者只放在容器构件。
- 如果只想遍历叶子节点,不想遍历容器,怎么办? → 在
Component接口中定义isLeaf()方法,或使用访问者模式对不同节点类型分别处理。 - 组合模式会导致大对象产生吗?如何解决? → 会。解决方式是懒加载(只加载当前层,展开时再加载子节点)或分页处理。
- Java 中哪里用了组合模式? → AWT/Swing 的
Container和Component、Spring Security 的SecurityConfigurer。
🎉 恭喜:如果你能立刻画出文件系统的树形结构,说出透明组合与安全组合的区别,并理解 Spring Security 为什么用
HttpSecurity.apply()统一处理配置,你已经掌握了组合模式的核心。面试中遇到“如何统一处理树形结构”的问题,这将是你的完美答案。
11. 六大设计原则在组合模式中的体现
| 设计原则 | 在组合模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 安全组合中,叶子只负责自身业务,容器负责管理子节点和业务委托 |
| 开闭原则(OCP) | 新增组件类型(如 ShortcutNode)无需修改现有代码 |
| 里氏替换原则(LSP) | 任何 Component 子类都可替换父类,不破坏递归逻辑 |
| 依赖倒置原则(DIP) | 客户端依赖抽象 Component,不依赖具体叶子或容器 |
| 接口隔离原则(ISP) | 安全组合通过精简的 Component 接口遵守 ISP,透明组合则有所违反 |
| 迪米特法则(LoD) | 客户端只与 Component 交互,不了解内部层次细节 |
12. 总结
组合模式是处理树形层次结构的首选方案,它通过统一叶子与容器的接口,让客户端可以一致地处理单个对象和对象集合。在文件系统、菜单、权限树等场景中,组合模式能大幅简化递归操作,提升系统的可扩展性。
✅ 最终建议:遇到“部分-整体”层次结构时,优先使用安全组合模式,将管理子节点的方法放在容器中;只有在需要完全透明且能容忍运行时异常时,才采用透明组合。如果树深度过大,配合懒加载或迭代器模式使用,避免栈溢出。
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:Java 23 种设计模式:从踩坑到精通 | 桥接模式 —— 类爆炸?试试分离抽象与实现
- 当前:Java 23 种设计模式:从踩坑到精通 | 组合模式 —— 树形结构处理,部分与整体一视同仁(你在这里)
- 下一篇:Java 23 种设计模式:从踩坑到精通 | 装饰器模式 —— 比继承更灵活的扩展方式,你用过吗? 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:装饰器模式 —— 比继承更灵活的扩展方式,你用过吗?🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)