定义

组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。主要用于数据能够用树形结构来表示并且相关业务能通过遍历树形结构来实现的场景。

结构

主要角色

  1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式和安全式的组合模式。

透明方式结构图

在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。

组合模式(Composite Pattern) - 图1

安全方式结构图

在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

组合模式(Composite Pattern) - 图2

代码演示

文件系统

文件系统利用组合模式递归计算文件数量和大小

  1. public abstract class FileSystemNode {
  2. protected String path;
  3. public FileSystemNode(String path) {
  4. this.path = path;
  5. }
  6. public abstract int countNumOfFiles();
  7. public abstract long countSizeOfFiles();
  8. public String getPath() {
  9. return path;
  10. }
  11. }
  12. public class File extends FileSystemNode {
  13. public File(String path) {
  14. super(path);
  15. }
  16. @Override
  17. public int countNumOfFiles() {
  18. return 1;
  19. }
  20. @Override
  21. public long countSizeOfFiles() {
  22. java.io.File file = new java.io.File(path);
  23. if (!file.exists()) return 0;
  24. return file.length();
  25. }
  26. }
  27. public class Directory extends FileSystemNode {
  28. private List<FileSystemNode> subNodes = new ArrayList<>();
  29. public Directory(String path) {
  30. super(path);
  31. }
  32. @Override
  33. public int countNumOfFiles() {
  34. int numOfFiles = 0;
  35. for (FileSystemNode fileOrDir : subNodes) {
  36. numOfFiles += fileOrDir.countNumOfFiles();
  37. }
  38. return numOfFiles;
  39. }
  40. @Override
  41. public long countSizeOfFiles() {
  42. long sizeofFiles = 0;
  43. for (FileSystemNode fileOrDir : subNodes) {
  44. sizeofFiles += fileOrDir.countSizeOfFiles();
  45. }
  46. return sizeofFiles;
  47. }
  48. public void addSubNode(FileSystemNode fileOrDir) {
  49. subNodes.add(fileOrDir);
  50. }
  51. public void removeSubNode(FileSystemNode fileOrDir) {
  52. int size = subNodes.size();
  53. int i = 0;
  54. for (; i < size; ++i) {
  55. if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
  56. break;
  57. }
  58. }
  59. if (i < size) {
  60. subNodes.remove(i);
  61. }
  62. }
  63. }

部门员工

利用组合模式计算部门和员工的工资

  1. public abstract class HumanResource {
  2. protected long id;
  3. protected double salary;
  4. public HumanResource(long id) {
  5. this.id = id;
  6. }
  7. public long getId() {
  8. return id;
  9. }
  10. public abstract double calculateSalary();
  11. }
  12. public class Employee extends HumanResource {
  13. public Employee(long id, double salary) {
  14. super(id);
  15. this.salary = salary;
  16. }
  17. @Override
  18. public double calculateSalary() {
  19. return salary;
  20. }
  21. }
  22. public class Department extends HumanResource {
  23. private List<HumanResource> subNodes = new ArrayList<>();
  24. public Department(long id) {
  25. super(id);
  26. }
  27. @Override
  28. public double calculateSalary() {
  29. double totalSalary = 0;
  30. for (HumanResource hr : subNodes) {
  31. totalSalary += hr.calculateSalary();
  32. }
  33. this.salary = totalSalary;
  34. return totalSalary;
  35. }
  36. public void addSubNode(HumanResource hr) {
  37. subNodes.add(hr);
  38. }
  39. }
  40. // 构建组织架构的代码
  41. public class Demo {
  42. private static final long ORGANIZATION_ROOT_ID = 1001;
  43. private DepartmentRepo departmentRepo; // 依赖注入
  44. private EmployeeRepo employeeRepo; // 依赖注入
  45. public void buildOrganization() {
  46. Department rootDepartment = new Department(ORGANIZATION_ROOT_ID);
  47. buildOrganization(rootDepartment);
  48. }
  49. private void buildOrganization(Department department) {
  50. List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department.getId());
  51. for (Long subDepartmentId : subDepartmentIds) {
  52. Department subDepartment = new Department(subDepartmentId);
  53. department.addSubNode(subDepartment);
  54. buildOrganization(subDepartment);
  55. }
  56. List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId());
  57. for (Long employeeId : employeeIds) {
  58. double salary = employeeRepo.getEmployeeSalary(employeeId);
  59. department.addSubNode(new Employee(employeeId, salary));
  60. }
  61. }
  62. }