8 『组合模式』 - 图1image.png

1. 「引例」

  • 举例:公司成员结构:总经理->研发部经历->员工。即 根节点->树枝节点->树叶节点。

image.png

  • 「根节点接口」

    1. public interface IRoot {
    2. // 得到总经理的信息
    3. public String getInfo();
    4. // 总经理下边要有小兵,那要能增加小兵,比如研发部总经理,这是个树枝节点
    5. public void add(IBranch branch);
    6. // 那要能增加树叶节点
    7. public void add(ILeaf leaf);
    8. // 既然能增加,那还要能够遍历,不可能总经理不知道他手下有哪些人
    9. public ArrayList getSubordinateInfo();
    10. }
  • 「根节点实现」

    1. public class Root implements IRoot {
    2. //保存根节点下的树枝节点和树叶节点,Subordinate的意思是下级
    3. private ArrayList subordinateList = new ArrayList();
    4. // 根节点的名称
    5. private String name = "";
    6. // 根节点的职位
    7. private String position = "";
    8. // 根节点的薪水
    9. private int salary = 0;
    10. // 通过构造函数传递进来总经理的信息
    11. public Root(String name, String position, int salary) {
    12. this.name = name;
    13. this.position = position;
    14. this.salary = salary;
    15. }
    16. //增加树枝节点
    17. public void add(IBranch branch) {
    18. this.subordinateList.add(branch);
    19. }
    20. //增加叶子节点,比如秘书,直接隶属于总经理
    21. public void add(ILeaf leaf) {
    22. this.subordinateList.add(leaf);
    23. }
    24. //得到自己的信息
    25. public String getInfo() {
    26. String info = "";
    27. info = "名称:" + this.name;
    28. info = info + "\t职位:" + this.position;
    29. info = info + "\t薪水: " + this.salary;
    30. return info;
    31. }
    32. //得到下级的信息
    33. public ArrayList getSubordinateInfo() {
    34. return this.subordinateList;
    35. }
    36. }
  • 「其他含分支节点接口」

    1. public interface IBranch {
    2. //获得信息
    3. public String getInfo();
    4. // 增加数据节点,例如研发部下设的研发一组
    5. public void add(IBranch branch);
    6. // 增加叶子节点
    7. public void add(ILeaf leaf);
    8. // 获得下级信息
    9. public ArrayList getSubordinateInfo();
    10. }
  • 「分支实现」

    1. public class Branch implements IBranch {
    2. //存储子节点的信息
    3. private ArrayList subordinateList = new ArrayList();
    4. // 树枝节点的名称
    5. private String name = "";
    6. // 树枝节点的职位
    7. private String position = "";
    8. // 树枝节点的薪水
    9. private int salary = 0;
    10. // 通过构造函数传递树枝节点的参数
    11. public Branch(String name, String position, int salary) {
    12. this.name = name;
    13. this.position = position;
    14. this.salary = salary;
    15. }
    16. //增加一个子树枝节点
    17. public void add(IBranch branch) {
    18. this.subordinateList.add(branch);
    19. }
    20. //增加一个叶子节点
    21. public void add(ILeaf leaf) {
    22. this.subordinateList.add(leaf);
    23. }
    24. //获得自己树枝节点的信息
    25. public String getInfo() {
    26. String info = "";
    27. info = "名称:" + this.name;
    28. info = info + "\t职位:" + this.position;
    29. info = info + "\t薪水:" + this.salary;
    30. return info;
    31. }
    32. //获得下级的信息
    33. public ArrayList getSubordinateInfo() {
    34. return this.subordinateList;
    35. }
    36. }
  • 「叶子接口」

    1. public interface ILeaf {
    2. //获得自己的信息
    3. public String getInfo();
    4. }
  • 「叶子实现」

    1. public class Leaf implements ILeaf {
    2. // 叶子叫什么名字
    3. private String name = "";
    4. // 叶子的职位
    5. private String position = "";
    6. // 叶子的薪水
    7. private int salary = 0;
    8. // 通过构造函数传递信息
    9. public Leaf(String name, String position, int salary) {
    10. this.name = name;
    11. this.position = position;
    12. this.salary = salary;
    13. }
    14. //最小的小兵只能获得自己的信息了
    15. public String getInfo() {
    16. String info = "";
    17. info = "名称:" + this.name;
    18. info = info + "\t职位:" + this.position;
    19. info = info + "\t薪水:" + this.salary;
    20. return info;
    21. }
    22. }
  • 测试太长了,就是测试上述的程序,可自行去git仓库上看。唯一要说的是打印下属的信息,需要递归打印

    1. //遍历所有的树枝节点,打印出信息
    2. private static String getAllSubordinateInfo(ArrayList subordinateList) {
    3. int length = subordinateList.size();
    4. for (int m = 0; m < length; m++) { //定义一个ArrayList长度,不要在for循环中每次计算
    5. Object s = subordinateList.get(m);
    6. if (s instanceof Leaf) { //是个叶子节点,也就是员工
    7. ILeaf employee = (ILeaf) s;
    8. System.out.println(((Leaf) s).getInfo());
    9. } else {
    10. IBranch branch = (IBranch) s;
    11. System.out.println(branch.getInfo());
    12. //再递归调用
    13. getAllSubordinateInfo(branch.getSubordinateInfo());
    14. }
    15. }
    16. return null;
    17. }
  • 现在需要解决问题。

    • getInfo 每个接口都有,可以抽象出来。
    • Root类Branch类可以合并,因为根节点本身就是树枝节点的一种。

image.png

  • 增加一个ICorp接口,是全公司人员信息接口类,大家都可以获取信息。
  • 这里的ILeaf是空接口,这里的意义何在?系统扩容时就会体现它的作用!

  • 「公司人员接口」

    1. public interface ICorp {
    2. // 每个员工都有信息
    3. public String getInfo();
    4. }
  • 「树叶接口」

    1. public interface ILeaf extends ICorp{}
  • 「树叶实现」

    1. public class Leaf implements ILeaf {
    2. //小兵也有名称
    3. private String name = "";
    4. //小兵也有职位
    5. private String position = "";
    6. //小兵也有薪水,否则谁给你干
    7. private int salary = 0;
    8. //通过一个构造函数传递小兵的信息
    9. public Leaf(String name, String position, int salary) {
    10. this.name = name;
    11. this.position = position;
    12. this.salary = salary;
    13. }
    14. //获得小兵的信息
    15. public String getInfo() {
    16. String info = "";
    17. info = "姓名:" + this.name;
    18. info = info + "\t职位:" + this.position;
    19. info = info + "\t薪水:" + this.salary;
    20. return info;
    21. }
    22. }
  • 「树枝接口」

    1. public interface IBranch extends ICorp{
    2. //能够增加小兵(树叶节点)或者是经理(树枝节点)
    3. public void addSubordinate(ICorp corp);
    4. //能够获得下属的信息
    5. public ArrayList<ICorp> getSubordinate();
    6. }
  • 「树枝实现」

    1. public class Branch implements IBranch {
    2. //领导也是人,也有名字
    3. private String name = "";
    4. //领导和领导不同,也是职位区别
    5. private String position = "";
    6. //领导也是拿薪水的
    7. private int salary = 0;
    8. //领导下边有那些下级领导和小兵
    9. ArrayList<ICorp> subordinateList = new ArrayList<ICorp>();
    10. //通过构造函数传递领导的信息
    11. public Branch(String name, String position, int salary) {
    12. this.name = name;
    13. this.position = position;
    14. this.salary = salary;
    15. }
    16. //增加一个下属,可能是小头目,也可能是个小兵
    17. public void addSubordinate(ICorp corp) {
    18. this.subordinateList.add(corp);
    19. }
    20. //我有哪些下属
    21. public ArrayList<ICorp> getSubordinate() {
    22. return this.subordinateList;
    23. }
    24. //领导也是人,他也有信息
    25. public String getInfo() {
    26. String info = "";
    27. info = "姓名:" + this.name;
    28. info = info + "\t职位:" + this.position;
    29. info = info + "\t薪水:" + this.salary;
    30. return info;
    31. }
    32. }
  • 再进一步优化,LeafBranch都有getInfo 方法,抽象出来

image.png

  • 接口没了,改成抽象类。IBranch接口没了。方法都在实现类中,场景类只认定抽象类Corp。

  • 「抽象公司职员类」

    1. public abstract class Corp {
    2. //公司每个人都有名称
    3. private String name = "";
    4. //公司每个人都职位
    5. private String position = "";
    6. //公司每个人都有薪水
    7. private int salary = 0;
    8. /**
    9. * 通过接口的方式传递,我们改变一下习惯,传递进来的参数名以下划线开始
    10. * 这个在一些开源项目中非常常见,一般构造函数都是定义的
    11. */
    12. public Corp(String _name, String _position, int _salary) {
    13. this.name = _name;
    14. this.position = _position;
    15. this.salary = _salary;
    16. }
    17. //获得员工信息
    18. public String getInfo() {
    19. String info = "";
    20. info = "姓名:" + this.name;
    21. info = info + "\t职位:" + this.position;
    22. info = info + "\t薪水:" + this.salary;
    23. return info;
    24. }
    25. }
  • 「树叶节点」

    1. public class Leaf extends Corp {
    2. //就写一个构造函数,这个是必须的
    3. public Leaf(String _name, String _position, int _salary) {
    4. super(_name, _position, _salary);
    5. }
    6. }
  • 「树枝节点」

    1. public class Branch extends Corp {
    2. //领导下边有那些下级领导和小兵
    3. ArrayList<Corp> subordinateList = new ArrayList<Corp>();
    4. //构造函数是必须的
    5. public Branch(String _name, String _position, int _salary) {
    6. super(_name, _position, _salary);
    7. }
    8. //增加一个下属,可能是小头目,也可能是个小兵
    9. public void addSubordinate(Corp corp) {
    10. this.subordinateList.add(corp);
    11. }
    12. //有哪些下属
    13. public ArrayList<Corp> getSubordinate() {
    14. return this.subordinateList;
    15. }
    16. }

2. 「定义」

Compose objects into tree structures to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.

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

通用类图
image.png-

  • Component抽象构件角色
    • 定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性,比如例子 中的getInfo就封装到了抽象类中。
  • Leaf叶子构件
    • 叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。
  • Composite树枝构件
    • 树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。
  1. public abstract class Component {
  2. //个体和整体都具有的共享
  3. public void doSomething() {
  4. //编写业务逻辑
  5. }
  6. }
  7. public class Composite extends Component {
  8. //构件容器
  9. private ArrayList<Component> componentArrayList = new ArrayList<Component>();
  10. //增加一个叶子构件或树枝构件
  11. public void add(Component component) {
  12. this.componentArrayList.add(component);
  13. }
  14. //删除一个叶子构件或树枝构件
  15. public void remove(Component component) {
  16. this.componentArrayList.remove(component);
  17. }
  18. //获得分支下的所有叶子构件和树枝构件
  19. public ArrayList<Component> getChildren() {
  20. return this.componentArrayList;
  21. }
  22. }
  23. public class Leaf extends Component{
  24. /* 可以覆写父类方法 public void doSomething() */
  25. }
  26. public class Client {
  27. public static void main(String[] args) {
  28. //创建一个根节点
  29. Composite root = new Composite();
  30. root.doSomething();
  31. //创建一个树枝构件
  32. Composite branch = new Composite();
  33. //创建一个叶子节点
  34. Leaf leaf = new Leaf();
  35. //建立整体
  36. root.add(branch);
  37. branch.add(leaf);
  38. }
  39. //通过递归遍历树
  40. public static void display(Composite root) {
  41. for (Component c : root.getChildren()) {
  42. if (c instanceof Leaf) { //叶子节点
  43. c.doSomething();
  44. } else { //树枝节点
  45. display((Composite) c);
  46. }
  47. }
  48. }
  49. }

3. 「应用」

1. 「优点」

  • 高层模块调用简单
    • 一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别
    • 也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化高层模块的代码。
  • 节点自由增加

    • 使用了组合模式后,如果想增加一个树枝节点、树叶节点很容 易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。

      2. 「缺点」

      组合模式有一个非常明显的缺点,看到在场景类中的定义,提到树叶和树枝使用时 的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与「依赖倒置原则」冲突,在使用的时候要考虑清楚,它限制了你接口的影响范围。

      3. 「使用场景」

  • 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。

  • 从一个整体中能够独立出部分模块或功能的场景。

4. 「注意事项」

📌只要是树形结构,就要考虑使用组合模式,这个一定要记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。


4. 「扩展」