1. 简介
本文将带你快速掌握 Java 中的组合设计模式(Composite Pattern)。
这个模式在处理树形结构时特别实用,比如组织架构、文件系统、UI 组件树等。它的核心思想是:✅ 让你统一处理单个对象和对象的组合,无需关心当前操作的是“叶子”还是“容器”。
掌握它,能帮你写出更清晰、扩展性更强的代码,尤其是在构建层级结构时,避免一堆 instanceof 判断和 if-else 踩坑。
2. 模式结构
组合模式的本质是一个树形结构,其中所有节点都实现同一个接口或继承同一个抽象类,从而对外提供一致的操作方式。
它通常由以下四个角色组成:
- Component(组件):基础接口或抽象类,定义所有节点共有的行为(如操作子节点、获取信息等)。客户端通过它来统一操作所有对象。
- Leaf(叶子):最底层的节点,没有子节点。它实现 Component 接口,但不会包含其他 Component。
- Composite(组合):容器节点,可以包含多个子节点(Leaf 或其他 Composite)。它实现 Component 接口,并管理子节点的增删、遍历等操作。
- Client(客户端):通过 Component 接口与整个结构交互,无需关心具体是 Leaf 还是 Composite。
✅ 核心优势:客户端代码完全透明,调用
printDepartmentName()
时,不管是调用 HeadDepartment 还是 SalesDepartment,代码写法一致。
3. 实战示例
我们以公司部门的层级结构为例:总公司(HeadDepartment)下辖多个子部门(如 SalesDepartment、FinancialDepartment),而子部门不再包含其他部门。
3.1 组件接口(Component)
定义所有部门共有的行为:
public interface Department {
void printDepartmentName();
}
这个接口就是所有部门的“统一入口”,不管是单个部门还是部门集合,都必须实现它。
3.2 叶子节点(Leaf)
叶子部门是最基础的单位,不包含子部门。
财务部示例:
public class FinancialDepartment implements Department {
private Integer id;
private String name;
public FinancialDepartment(Integer id, String name) {
this.id = id;
this.name = name;
}
public void printDepartmentName() {
System.out.println(getClass().getSimpleName());
}
// getters and setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
销售部示例:
public class SalesDepartment implements Department {
private Integer id;
private String name;
public SalesDepartment(Integer id, String name) {
this.id = id;
this.name = name;
}
public void printDepartmentName() {
System.out.println(getClass().getSimpleName());
}
// getters and setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
⚠️ 注意:这两个类是典型的 Leaf,它们不持有其他 Department 实例,只实现自己的行为。
3.3 组合节点(Composite)
HeadDepartment 作为总公司,可以管理多个子部门:
import java.util.ArrayList;
import java.util.List;
public class HeadDepartment implements Department {
private Integer id;
private String name;
private List<Department> childDepartments;
public HeadDepartment(Integer id, String name) {
this.id = id;
this.name = name;
this.childDepartments = new ArrayList<>();
}
public void printDepartmentName() {
childDepartments.forEach(Department::printDepartmentName);
}
public void addDepartment(Department department) {
childDepartments.add(department);
}
public void removeDepartment(Department department) {
childDepartments.remove(department);
}
// getters and setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
✅ 关键点:
childDepartments
存的是Department
接口,实现了多态。printDepartmentName()
不是自己打印,而是委托给所有子节点去打印,这就是递归遍历的起点。- 提供了
add
/remove
方法,用于动态维护树结构。
4. 测试验证
写个简单的 Demo 来验证组合行为是否正常:
public class CompositeDemo {
public static void main(String[] args) {
Department salesDepartment = new SalesDepartment(
1, "Sales department");
Department financialDepartment = new FinancialDepartment(
2, "Financial department");
HeadDepartment headDepartment = new HeadDepartment(
3, "Head department");
headDepartment.addDepartment(salesDepartment);
headDepartment.addDepartment(financialDepartment);
headDepartment.printDepartmentName();
}
}
输出结果:
SalesDepartment
FinancialDepartment
✅ 成功!调用
headDepartment.printDepartmentName()
后,自动遍历并打印了所有子部门的名称。客户端代码无需知道内部是叶子还是容器,简单粗暴地调用即可。
5. 总结
组合设计模式通过统一接口,将“个体”与“整体”无缝衔接,特别适合处理树形结构。
它的优势在于:
- ✅ 客户端代码简洁,透明操作所有节点
- ✅ 扩展性强,新增 Leaf 或 Composite 不影响现有逻辑
- ✅ 符合开闭原则和单一职责原则
当然也要注意:
- ❌ 不适合所有树结构,如果 Leaf 和 Composite 行为差异太大,强行统一反而复杂
- ❌ 管理复杂层级时,需注意循环引用问题
项目源码已托管至 GitHub:https://github.com/baeldung/design-patterns-structural