组合模式也称为部分整体模式,是结构型设计模式的一种。
组合模式将一组相似的对象看作一个对象进行处理,这组对象是根据一个树状结构进行组合的,处理时提供一个统一的方法来访问相应的对象,使得可以忽略对象与对象组之间的差异。
举一个跟公司相关的例子:

  • 一个公司里会有一个「首席执行官」
  • 一个「首席执行官」会管理着多个「部门经理」
  • 一个「部门经理」会管理着多个「普通员工」

但其实对公司而言,不管是「首席执行官」还是「部门经理」还是「普通员工」,它们的本质都是公司的员工。示意图如下:
组合模式 - 图1

代码示例

上面举的公司员工这个栗子,如果想通过组合模式进行编码的话,姿势是酱紫的:

  1. public class Employee {
  2. private String mName;
  3. private String mPosition;
  4. private int mSalary;
  5. private List<Employee> mSubordinates;
  6. public Employee(String name, String position, int salary) {
  7. mName = name;
  8. mPosition = position;
  9. mSalary = salary;
  10. mSubordinates = new ArrayList<>();
  11. }
  12. // name, position, salary getter and setter ...
  13. // 增加管理的员工
  14. public void add(Employee employee) {
  15. mSubordinates.add(employee);
  16. }
  17. // 移除管理的员工
  18. public void remove(Employee employee) {
  19. mSubordinates.remove(employee);
  20. }
  21. // 获取管理的员工列表
  22. public List<Employee> getSubordinates() {
  23. return mSubordinates;
  24. }
  25. // 获取管理的员工列表
  26. public String getSubordinateNames() {
  27. if (mSubordinates.isEmpty()) {
  28. return "无";
  29. }
  30. StringBuilder builder = new StringBuilder();
  31. for (Employee e : mSubordinates) {
  32. builder.append(e.getName()).append(",");
  33. }
  34. String result = builder.toString();
  35. return result.substring(0, result.length() - 1);
  36. }
  37. // 获取自己以及管理的员工的总工资
  38. public int getSubordinateTotalSalary() {
  39. int result = mSalary;
  40. for (Employee e : mSubordinates) {
  41. result += e.getSubordinateTotalSalary();
  42. }
  43. return result;
  44. }
  45. }

上面的 Employee 是我们抽象出来的公司员工类。接下来我们写一段测试代码,来构建出一个公司里面的 CEO、部门经理、普通员工等人员,并验证我们提供的 “统一访问对象的方法” 逻辑是否正确:

  1. public static void main(String[] args) {
  2. Employee ceo = new Employee("张三", "CEO", 30000);
  3. Employee salesManager = new Employee("李四", "销售部经理", 20000);
  4. Employee marketingManager = new Employee("王五", "市场部经理", 20000);
  5. Employee sale1 = new Employee("销售员工1", "销售员工", 10000);
  6. Employee sale2 = new Employee("销售员工2", "销售员工", 11000);
  7. Employee marketing1 = new Employee("市场员工1", "市场员工", 10000);
  8. Employee marketing2 = new Employee("市场员工2", "市场员工", 11000);
  9. Employee marketing3 = new Employee("市场员工3", "市场员工", 12000);
  10. salesManager.add(sale1);
  11. salesManager.add(sale2);
  12. marketingManager.add(marketing1);
  13. marketingManager.add(marketing2);
  14. marketingManager.add(marketing3);
  15. ceo.add(salesManager);
  16. ceo.add(marketingManager);
  17. System.out.println("张三管理的员工有:" + ceo.getSubordinateNames());
  18. System.out.println("李四管理的员工有:" + salesManager.getSubordinateNames());
  19. System.out.println("王五管理的员工有:" + marketingManager.getSubordinateNames());
  20. System.out.println("----------------");
  21. System.out.println("张三及其管理的员工总工资:" + ceo.getSubordinateTotalSalary());
  22. System.out.println("李四及其管理的员工总工资:" + salesManager.getSubordinateTotalSalary());
  23. System.out.println("王五及其管理的员工总工资:" + marketingManager.getSubordinateTotalSalary());
  24. System.out.println("----------------");
  25. System.out.println("销售员工1管理的员工有:" + sale1.getSubordinateNames());
  26. System.out.println("销售员工1及其管理的员工总工资:" + sale1.getSubordinateTotalSalary());
  27. System.out.println("----------------");
  28. marketingManager.remove(marketing1);
  29. System.out.println("市场员工1离职后");
  30. System.out.println("王五管理的员工有:" + marketingManager.getSubordinateNames());
  31. System.out.println("王五及其管理的员工总工资:" + marketingManager.getSubordinateTotalSalary());
  32. }

日志输出是这样的:

  1. 张三管理的员工有:李四,王五
  2. 李四管理的员工有:销售员工1,销售员工2
  3. 王五管理的员工有:市场员工1,市场员工2,市场员工3
  4. ----------------
  5. 张三及其管理的员工总工资:124000
  6. 李四及其管理的员工总工资:41000
  7. 王五及其管理的员工总工资:53000
  8. ----------------
  9. 销售员工1管理的员工有:无
  10. 销售员工1及其管理的员工总工资:10000
  11. ----------------
  12. 市场员工1离职后
  13. 王五管理的员工有:市场员工2,市场员工3
  14. 王五及其管理的员工总工资:43000

由此可见,尽管 Employ 类的实现很简单,但是通过组合的形式可以构建出一个庞大的公司员工结构。

总结

组合模式类包含了一个本身对象的集合,并提供了访问该集合的统一方法。
意图:

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

主要解决:

  • 在树型结构的问题中,模糊了简单元素和复杂元素的概念。
  • 客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构进行解耦。

如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
关键逻辑:树枝内部组合该接口,并且含有内部属性 List,里面放 Component
优点:高层模块调用简单,节点可自由增加。
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景:「部分-整体」相关场景,如树形菜单、文件及文件夹的管理等。
注意事项:定义时为具体类。