模式说明

一系列对象可以用树来描述,形成树-子树-叶子的层次机构。如一家大型公司,根节点是总公司,下设若干子公司和若干非公司部门如总公司财经部,总公司人力资源部门。子公司中如同总公司结构,也包含它自己的子公司和非公司部门。则子公司对象是所属公司对象的子树,非公司部门对象是所属公司对象的叶子。客户端对单个对象和组合对象具有一致的访问性。即上层公司A(根)和其子公司B(树枝或子树),其非子公司部门C(树叶)继承同一个抽象构件类,有相同的方法。但因为树叶不再有子节点,根据树叶中是否包含针对子节点的方法(例如add增加儿子,removegetChild等),分为透明方式和安全方式。

本示例以总公司和子公司场景为例,演示透明方式的组合模式。在客户端中将树枝和树叶机构加入到根(总公司)中,然后调用总公司的方法,实现对其下属机构的递归调用。

透明方式
树叶与树枝具有同样的,在抽象构件类中声明的针对子节点的方法时,对客户端而言,一个对象是树枝还是树叶是透明的,客户端无需关心。这种方式下,树叶无儿子,却仍需实现针对儿子的各种方法(空方法或抛出异常),存在安全性问题。
安全方式
与透明方式相对,不在抽象构件类和树叶实现类中声明针对儿子的方法,只在树枝中实现。缺点是客户端需要提前知道哪些对象是树枝,哪些是树叶,失去透明性。

结构

抽象构件类
树枝和树叶的抽象类,声明了公共抽象方法,实现默认行为。
具体实现树枝类
继承抽象构件类,具有儿子的对象,包含针对儿子的各种方法。
具体实现树叶类
继承抽象构件,不具有儿子的对象。若包含针对儿子的各种方法为透明方式,不包含则为安全模式。

代码演示

  1. package com.yukiyama.pattern.structure;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /**
  5. * 组合模式
  6. */
  7. public class CompositeDemo {
  8. public static void main(String[] args) {
  9. // 声明上海总公司
  10. Company hq = new ConcreteCompany();
  11. hq.setName("上海总公司");
  12. // 声明上海总公司的叶子机构并添加到总公司中
  13. Company hqHR = new HRDepartment();
  14. hqHR.setName("上海总公司人力资源部");
  15. hq.add(hqHR);
  16. Company hqFi = new FinanceDepartment();
  17. hqFi.setName("上海总公司财务部");
  18. hq.add(hqFi);
  19. // 声明上海总公司的树枝机构广州分公司,并完成其下属机构的添加
  20. Company gzSub = new ConcreteCompany();
  21. gzSub.setName("广州分公司");
  22. Company gzHR = new HRDepartment();
  23. gzHR.setName("广州分公司人力资源部");
  24. gzSub.add(gzHR);
  25. Company gzFi = new FinanceDepartment();
  26. gzFi.setName("广州分公司财务部");
  27. gzSub.add(gzFi);
  28. // 将广州分公司添加到上海总公司
  29. hq.add(gzSub);
  30. // 调用总公司hq的display()方法,递归调用其下分支机构的display()方法
  31. System.out.println("====机构====");
  32. hq.display();
  33. // 调用总公司hq的duty()方法,递归调用其下分支机构的duty()方法
  34. System.out.println("====职责====");
  35. hq.duty();
  36. }
  37. }
  38. /**
  39. * 抽象构件类
  40. * 持有实例字段和非抽象实例方法,定义抽象方法。
  41. * 下例以公司为抽象构件类。
  42. */
  43. abstract class Company{
  44. private String name;
  45. public void setName(String name) {
  46. this.name = name;
  47. }
  48. public String getName() {
  49. return name;
  50. }
  51. public abstract void add(Company com);
  52. public abstract void remove(Company com);
  53. public abstract void display();
  54. public abstract void duty();
  55. }
  56. /**
  57. * 树枝类
  58. * 继承抽象构件类,比抽象类多一个用于保存儿子的List,实现抽象方法。
  59. */
  60. class ConcreteCompany extends Company{
  61. private List<Company> subs = new ArrayList<>();
  62. @Override
  63. public void add(Company com) {
  64. subs.add(com);
  65. }
  66. @Override
  67. public void remove(Company com) {
  68. subs.remove(com);
  69. }
  70. @Override
  71. public void display() {
  72. System.out.println(this.getName());
  73. for(Company com : subs) {
  74. com.display();
  75. }
  76. }
  77. @Override
  78. public void duty() {
  79. System.out.printf("%s,统筹公司所有事务。\n", this.getName());
  80. for(Company com : subs) {
  81. com.duty();
  82. }
  83. }
  84. }
  85. /**
  86. * 树叶类
  87. * 继承抽象构件类,实现抽象方法,但对于针对儿子的方法,被调用时打印不支持
  88. * 操作的提示。
  89. * 下例是HR部门类。
  90. */
  91. class HRDepartment extends Company{
  92. @Override
  93. public void add(Company com) {
  94. System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
  95. }
  96. @Override
  97. public void remove(Company com) {
  98. System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
  99. }
  100. @Override
  101. public void display() {
  102. System.out.println(this.getName());
  103. }
  104. @Override
  105. public void duty() {
  106. System.out.printf("%s,负责公司员工招聘薪酬管理。\n", this.getName());
  107. }
  108. }
  109. /**
  110. * 树叶类
  111. * 下例是财务部门类。
  112. */
  113. class FinanceDepartment extends Company{
  114. @Override
  115. public void add(Company com) {
  116. System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
  117. }
  118. @Override
  119. public void remove(Company com) {
  120. System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
  121. }
  122. @Override
  123. public void display() {
  124. System.out.println(this.getName());
  125. }
  126. @Override
  127. public void duty() {
  128. System.out.printf("%s,负责公司财务管理。\n", this.getName());
  129. }
  130. }