Skip to main content

组合模式 (Composite)

场景问题​

问题案例​

考虑这样一个实际的业务场景:公司组织架构的管理。一个公司有多个部门,每个部门下又可以有子部门或员工。比如:

  • 总公司下有财务部、技术部
  • 技术部下有开发组、测试组
  • 每个组下有具体的员工

现在需要实现一个功能:打印出整个公司的组织结构。

不用模式的解决方案​

实现简化版本​

最直接的做法是分别为部门和员工设计不同的类:

// 员工类
public class Employee {
private String name;
public Employee(String name) { this.name = name; }
public void print() {
System.out.println("员工:" + name);
}
}

// 部门类
import java.util.ArrayList;
import java.util.List;
public class Department {
private String name;
private List<Department> subDepartments = new ArrayList<>();
private List<Employee> employees = new ArrayList<>();
public Department(String name) { this.name = name; }
public void addDepartment(Department dept) { subDepartments.add(dept); }
public void addEmployee(Employee emp) { employees.add(emp); }
public void print() {
System.out.println("部门:" + name);
for (Employee e : employees) e.print();
for (Department d : subDepartments) d.print();
}
}

使用示例​

public class Client {
public static void main(String[] args) {
Department root = new Department("总公司");
Department tech = new Department("技术部");
Department dev = new Department("开发组");
Department test = new Department("测试组");
tech.addDepartment(dev);
tech.addDepartment(test);
root.addDepartment(tech);
root.addEmployee(new Employee("总经理"));
dev.addEmployee(new Employee("开发1号"));
test.addEmployee(new Employee("测试1号"));
root.print();
}
}

有何问题​

这种实现方式虽然可以工作,但存在明显问题:

  1. 客户端需要区分部门和员工,操作不统一。
  2. 如果要扩展为更多层级或类型,代码会变得复杂。
  3. 不能很好地支持树形结构的灵活扩展。

如果要对所有组织结构统一处理(比如递归打印、统计人数等),会非常不方便。


解决方案​

组合模式的定义​

将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

应用组合模式的思路​

  • 定义一个统一的接口(如OrganizationComponent),部门和员工都实现该接口。
  • 部门可以包含子部门和员工,员工为叶子节点。
  • 客户端只需操作统一接口,无需关心是部门还是员工。

组合模式的结构​

  • OrganizationComponent:抽象组件,定义统一接口。
  • Department:组合节点,可以包含子节点。
  • Employee:叶子节点,不能再包含子节点。

组合模式的实现​

import java.util.ArrayList;
import java.util.List;

// 抽象组件
public abstract class OrganizationComponent {
protected String name;
public OrganizationComponent(String name) { this.name = name; }
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
public abstract void print();
}

// 组合节点:部门
public class Department extends OrganizationComponent {
private List<OrganizationComponent> children = new ArrayList<>();
public Department(String name) { super(name); }
@Override
public void add(OrganizationComponent component) {
children.add(component);
}
@Override
public void remove(OrganizationComponent component) {
children.remove(component);
}
@Override
public void print() {
System.out.println("部门:" + name);
for (OrganizationComponent c : children) {
c.print();
}
}
}

// 叶子节点:员工
public class Employee extends OrganizationComponent {
public Employee(String name) { super(name); }
@Override
public void print() {
System.out.println(" 员工:" + name);
}
}

使用示例​

public class Client {
public static void main(String[] args) {
OrganizationComponent root = new Department("总公司");
OrganizationComponent tech = new Department("技术部");
OrganizationComponent dev = new Department("开发组");
OrganizationComponent test = new Department("测试组");
tech.add(dev);
tech.add(test);
root.add(tech);
root.add(new Employee("总经理"));
dev.add(new Employee("开发1号"));
test.add(new Employee("测试1号"));
root.print();
}
}

组合重写示例​

示例结构​

用mermaid画出公司组织结构的树形关系:


示例代码​

见上文"组合模式的实现"部分。


模式讲解​

组合模式的功能​

  • 统一了叶子节点和组合节点的操作,客户端无需区分。
  • 适用于树形结构的场景,如组织结构、文件系统、菜单等。

实现成接口​

  • 可以将抽象组件定义为接口或抽象类,具体实现视需求而定。
  • 叶子节点和组合节点都实现统一接口。

使用工厂方法​

  • 可以结合工厂方法模式,统一创建节点对象。

切换产品簇​

  • 组合模式可用于切换不同的树形结构产品簇,如不同类型的组织结构。

执行时序图​