0.参考资料



1.概述

  • 将对象组合成树形结构以表示 “部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。—《设计模式》GoF
  • 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
    • 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

1.1动机

  1. - 在软件在某些情况下,客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
  2. - 如何将“ 客户代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

1.2结构

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629536571085-27a3de83-2375-4c24-95a0-5c6ebc38fd00.png#clientId=uf1d2f655-6621-4&from=paste&height=218&id=u3be459e4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=435&originWidth=966&originalType=binary&ratio=1&size=194741&status=done&style=none&taskId=u627604ab-c365-4217-beb2-f4de205bce6&width=483)
  2. - 分析
  3. 1. Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件,Component可以是抽象类或者接口
  4. 1. Leaf:在组合中表示叶子节点,叶子节点没有子节点. 即是最底层节点
  5. 1. Composite :非叶子节点,用 于存储子部件,在 Component 口中实现子部件的相关操作,比如增加(add),删除。

2.要点总结

宏观架构

  1. 1. Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系, 使得客户代码可以一致地(复用)处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
  2. 1. "客户代码与复杂的对象容器结构”解耦是Composite的核心思想. 解耦之后,客户代码将与纯粹的抽象接口 -- 而非对象容器的内部实现结构 -- 发生依赖,从而更能 “应对变化”。
  3. 1. Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。(算法方面)

微观代码

  1. 1. 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  2. 1. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
  3. 1. 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点和叶子从而创建出复杂的树形结构
  4. 1. 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
  5. 1. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

3.案例

需求

  1. - 学校院系展示
  2. - 要在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系。
  3. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629602112190-154c84f5-05f7-46df-a2df-009f83f28a09.png#clientId=u93d6b159-7fd0-4&from=paste&height=95&id=ued1eeaa5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=189&originWidth=380&originalType=binary&ratio=1&size=5650&status=done&style=none&taskId=u8d5759e0-9f6e-4999-ad9c-11c815cec39&width=190)

方案

  1. - 使用层层继承方式

类图

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629602166233-80ce2899-1a2a-48da-94f4-d4f9cd5560a9.png#clientId=u93d6b159-7fd0-4&from=paste&height=253&id=u12b1c5eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=402&originalType=binary&ratio=1&size=25635&status=done&style=none&taskId=u7abc64df-8ea1-4b96-b5b5-3d76860e92b&width=201)

分析

  1. 1. 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  2. 1. 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
  3. 1. 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式

4.使用模式

方案

  1. - 组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子
  2. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629602577903-b01932c9-7dbb-4dd2-b78e-6a85d1cb9c3c.png#clientId=u93d6b159-7fd0-4&from=paste&height=156&id=u4ab8b864&margin=%5Bobject%20Object%5D&name=image.png&originHeight=311&originWidth=611&originalType=binary&ratio=1&size=18590&status=done&style=none&taskId=u91c80c64-d8ee-4e14-b645-08970198811&width=305.5)

类图

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629602603633-54f9d6a8-ec47-44d9-a899-2f07dfad8a61.png#clientId=u93d6b159-7fd0-4&from=paste&height=215&id=u47356dbd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=429&originWidth=1045&originalType=binary&ratio=1&size=199392&status=done&style=none&taskId=ubc609c82-d459-467c-afb8-3433a0a31fd&width=522.5)

代码

  1. - 节点基类
  1. /**
  2. 抽象基类, 定义了子类共通的成员
  3. */
  4. @Data
  5. public abstract class AbstComponent {
  6. String name;
  7. // 空实现方法. 若抽象方法, 则所有子类都需要实现, 包括不含有该功能叶子节点, 不合理;
  8. // 而空实现, 则子类有需要便子实现, 不影响叶子节点和其它子类
  9. public void add(AbstComponent component){
  10. throw new UnsupportedOperationException();
  11. };
  12. public void remove(AbstComponent component){
  13. throw new UnsupportedOperationException();
  14. }
  15. // 打印方法, 所有子类都有该功能, 且实现不同. 故抽象
  16. public abstract void print();
  17. }
  1. - 节点实现类
  1. /**
  2. 最高层, 组合了<AbstComponent>泛型的List, 实际内容是子层即第二层
  3. */
  4. public class ComponentLevel1 extends AbstComponent {
  5. // 实际存放的是下一层. 在本案例中是院系)
  6. List<AbstComponent> components = new ArrayList<AbstComponent>();
  7. // 本案例中, 子类的 add()/remove() 基本相同.
  8. // 但实际开发中差异可能巨大. 故也应该实现之, 而不是硬编码在基类中
  9. @Override
  10. public void add(AbstComponent component) {
  11. components.add(component);
  12. System.out.println("L1层 添加成功");
  13. }
  14. @Override
  15. public void remove(AbstComponent component) {
  16. components.remove(component);
  17. System.out.println("L1层 移除成功");
  18. }
  19. @Override
  20. public void print() {
  21. System.out.println(this.name);
  22. for (AbstComponent component : components) {
  23. component.print();
  24. }
  25. }
  26. }
  27. /**
  28. 第二层, 子层是最小层
  29. */
  30. public class ComponentLevel2 extends AbstComponent {
  31. // 实际存放的是下一层. 在本案例中是叶子节点: 具体专业(前端后端...)
  32. List<AbstComponent> components = new ArrayList<AbstComponent>();
  33. @Override
  34. public void add(AbstComponent component) {
  35. components.add(component);
  36. System.out.println("L2层 添加成功");
  37. }
  38. @Override
  39. public void remove(AbstComponent component) {
  40. components.remove(component);
  41. System.out.println("L2层 移除成功");
  42. }
  43. @Override
  44. public void print() {
  45. System.out.println("\t" + this.name);
  46. for (AbstComponent component : components) {
  47. component.print();
  48. }
  49. }
  50. }
  51. /**
  52. * 叶子节点, 最小层. 无需 add()/remove(). 故不重写, 使用父类的默认的(调用则报错)的 add()/remove()
  53. */
  54. public class ComponentLevel3 extends AbstComponent {
  55. @Override
  56. public void print() {
  57. System.out.println("\t\t" + this.name);
  58. }
  59. }
  1. - 测试
  1. public class Client {
  2. public static void main(String[] args) {
  3. // 根节点
  4. AbstComponent root = new ComponentLevel1();
  5. root.setName("清华大学");
  6. // 中间节点
  7. AbstComponent componentA = new ComponentLevel2();
  8. componentA.setName("信息工厂学院");
  9. AbstComponent componentB = new ComponentLevel2();
  10. componentB.setName("金融学院");
  11. // 叶子节点
  12. AbstComponent level3_A_1 = new ComponentLevel3();
  13. level3_A_1.setName("安卓");
  14. AbstComponent level3_A_2 = new ComponentLevel3();
  15. level3_A_2.setName("前端");
  16. AbstComponent level3_A_3 = new ComponentLevel3();
  17. level3_A_3.setName("运维");
  18. AbstComponent level3_B_1 = new ComponentLevel3();
  19. level3_B_1.setName("理财");
  20. // 开始填充
  21. root.add(componentA);
  22. root.add(componentB);
  23. componentA.add(level3_A_1);
  24. componentA.add(level3_A_2);
  25. componentA.add(level3_A_3);
  26. componentB.add(level3_B_1);
  27. // 打印测试
  28. System.out.println("-----------------------------");
  29. root.print();
  30. System.out.println("----------------------------测试2");
  31. componentA.print();
  32. }
  33. }
  1. L1 添加成功
  2. L1 添加成功
  3. L2 添加成功
  4. L2 添加成功
  5. L2 添加成功
  6. L2 添加成功
  7. -----------------------------
  8. 清华大学
  9. 信息工厂学院
  10. 安卓
  11. 前端
  12. 运维
  13. 金融学院
  14. 理财
  15. ----------------------------测试2
  16. 信息工厂学院
  17. 安卓
  18. 前端
  19. 运维

5.经典使用


5.1JKD中HashMap

分析

  - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629626276080-80a60cba-f77d-4de5-b7e1-0273d8eac07e.png#clientId=ue047f406-320d-4&from=paste&height=247&id=ud6563c66&margin=%5Bobject%20Object%5D&name=image.png&originHeight=494&originWidth=1449&originalType=binary&ratio=1&size=181253&status=done&style=none&taskId=u438dc5bf-5a83-4b53-ae22-07c244e2246&width=724.5)
  - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629626310680-f82ba0e5-c7be-4d03-a0ef-250c3bed98de.png#clientId=ue047f406-320d-4&from=paste&height=196&id=uf60d82fd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=391&originWidth=1008&originalType=binary&ratio=1&size=151351&status=done&style=none&taskId=u49d3a46d-3bf5-4975-a2c1-c2f0ce1f54e&width=504)