24种设计模式--组合模式【Composite Pattern】

  大家在上学的时候应该都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,我们上学那会儿这一章节是必考内容,左子树,右子树,什么先序遍历后序遍历什么,重点就是二叉树的的遍历,我还记得当时老师就说,考试的时候一定有二叉树的构建和遍历,现在想起来还是觉的老师是正确的,树状结果在实际项目应用的非常广泛。

  咱就先说个最常见的例子,公司的人事管理就是一个典型的树状结构,你想想你公司的结构是不是这样:

  从最高的老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构(说明一下,这不是二叉树,有关二叉树的定义可以翻翻以前的教科书),我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍,你要确认你建立的树是否有问题呀。

  从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工 A、员工 D 等),我们增加一点学术术语上去,总经理叫做根节点(是不是想到 XML 的那个根节点 root,那就对了),类似研发部经理有分支的节点叫做树枝节点,类似员工 A 的无分支的节点叫做树叶节点,都很形象,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:

  这个类图是初学者最容易想到的类图(如果你已经看明白这个类图的缺陷了,就可以不看下边的实现了,我是循序渐进的讲课,呵呵),那我们来看这个实现:

  先看最高级别的根节点的实现:

 1 package com.pattern.composite;
 2 
 3 import java.util.ArrayList;
 4 
 5 /**
 6  * 定义一个根节点,就为总经理服务
 7  * @author http://www.cnblogs.com/initial-road/
 8  *
 9  */
10 public interface IRoot {
11     
12     // 得到总经理的信息
13     public String getInfo();
14     
15     // 总经理下边要有小兵,那要能增加小兵,比如研发总经理,这是个树枝节点
16     public void add(IBranch branch);
17     
18     // 那要能增加树叶节点
19     public void add(ILeaf leaf);
20     
21     // 既然能增加,那要还要能够便遍历,不肯能总经理不知道他手下有哪些人
22     public ArrayList getSubordinateInfo();
23     
24 }
25 
26 // 这个根节点就是我们的总经理 CEO,然后看实现类:
27 
28 package com.pattern.composite;
29 
30 import java.util.ArrayList;
31 
32 /**
33  * 根节点的实现类
34  * @author http://www.cnblogs.com/initial-road/
35  *
36  */
37 @SuppressWarnings("unchecked")
38 public class Root implements IRoot {
39     // 保存根节点下的树枝节点和树叶节点,Subrdinate的意思是下级
40     private ArrayList subordinateList = new ArrayList();
41     
42     // 根节点的名称
43     private String name = "";
44     
45     // 根节点的职位
46     private String position = "";
47     
48     // 根节点的薪水
49     private int salary = 0;
50     
51     // 通过构造函数传递进来总经理的信息
52     public Root(String name, String position, int salary){
53         this.name = name;
54         this.position = position;
55         this.salary = salary;
56     }
57     
58     // 增加树枝节点
59     public void add(IBranch branch) {
60         this.subordinateList.add(branch);
61     }
62 
63     // 增加叶子节点,比如秘书,直接属于总经理
64     public void add(ILeaf leaf) {
65         this.subordinateList.add(leaf);
66     }
67     
68     // 得知自己的信息
69     public String getInfo() {
70         String info = "";
71         info = "名称:" + this.name;
72         info = info + "\t职位:" + this.position;
73         info = info + "\t薪水:" + this.salary;
74         return info;
75     }
76     
77     // 得到下级的信息
78     public ArrayList getSubordinateInfo() {
79         return this.subordinateList;
80     }
81 
82 }

  很简单,通过构造函数传入参数,然后获得信息,还可以增加子树枝节点(部门经理)和叶子节点(秘书)。我们再来看 IBranch.java:

  1 package com.pattern.composite;
  2 
  3 import java.util.ArrayList;
  4 
  5 /**
  6  * 树枝节点,也就是各个部门经理和组长的角色
  7  * @author http://www.cnblogs.com/initial-road/
  8  *
  9  */
 10 @SuppressWarnings("unchecked")
 11 public interface IBranch {
 12     
 13     // 获得信息
 14     public String getInfo();
 15     
 16     // 增加数据节点,例如研发部下的研发一组
 17     public void add(IBranch branch);
 18     
 19     // 增加叶子节点
 20     public void add(ILeaf leaf);
 21     
 22     // 获得下级信息
 23     public ArrayList getSubordinateInfo();
 24     
 25 }
 26 
 27 // 下面是树枝节点的实现类:
 28 package com.pattern.composite;
 29 
 30 import java.util.ArrayList;
 31 
 32 /**
 33  * 所有的树枝节点
 34  * @author http://www.cnblogs.com/initial-road/
 35  *
 36  */
 37 @SuppressWarnings("unchecked")
 38 public class Branch implements IBranch {
 39     // 存储子节点信息
 40     private ArrayList subordinateList = new ArrayList();
 41     
 42     // 树枝节点的名称
 43     private String name = "";
 44     
 45     // 树枝节点的职位
 46     private String position = "";
 47     
 48     // 树枝节点的薪水
 49     private int salary = 0;
 50     
 51     // 通过构造函数传递树枝节点的参数
 52     public Branch(String name, String position, int salary){
 53         this.name = name;
 54         this.position = position;
 55         this.salary = salary;
 56     }
 57     
 58     // 增加一个子树枝节点
 59     public void add(IBranch branch) {
 60         this.subordinateList.add(branch);
 61     }
 62 
 63     // 增加一个叶子节点
 64     public void add(ILeaf leaf) {
 65         this.subordinateList.add(leaf);
 66     }
 67 
 68     // 获得自己树枝节点的信息
 69     public String getInfo() {
 70         String info = "";
 71         info = "名称:" + this.name;
 72         info = info + "\t职位:" + this.position;
 73         info = info + "\t薪水:" + this.salary;
 74         return info;
 75     }
 76     
 77     // 获得下级的信息
 78     public ArrayList getSubordinateInfo() {
 79         return this.subordinateList;
 80     }
 81 
 82 }
 83 
 84 // 最后看叶子节点,也就是员工的接口:
 85 
 86 package com.pattern.composite;
 87 
 88 /**
 89  * 叶子节点,也就是最小的小兵了,只能自己干活,不能指派别人了
 90  * @author http://www.cnblogs.com/initial-road/
 91  *
 92  */
 93 public interface ILeaf {
 94     
 95     // 获得自己的信息呀
 96     public String getInfo();
 97     
 98 }
 99 
100 // 下面是叶子节点的实现类:
101 
102 package com.pattern.composite;
103 
104 /**
105  * 最小的叶子节点
106  * @author http://www.cnblogs.com/initial-road/
107  *
108  */
109 public class Leaf implements ILeaf {
110     // 叶子叫什么名字
111     private String name = "";
112     
113     // 叶子的职位
114     private String position = "";
115     
116     // 叶子的薪水
117     private int salary = 0;
118     
119     // 通过构造函数传递信息
120     public Leaf(String name, String position, int salary){
121         this.name = name;
122         this.position = position;
123         this.salary = salary;
124     }
125     
126     // 最小的小兵只能获得自己的信息了
127     public String getInfo() {
128         String info = "";
129         info = "名称:" + this.name;
130         info = info + "\t职位:" + this.position;
131         info = info + "\t薪水:" + this.salary;
132         return info;
133     }
134 
135 }

  好了,所有的根节点,树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现了,然后的工作就是组装成一个树状结构和遍历这个树状结构,看 Client.java 程序:

 1 package com.pattern.composite;
 2 
 3 import java.util.ArrayList;
 4 
 5 /**
 6  * Client的作用是组装这棵树,并遍历一遍
 7  * @author http://www.cnblogs.com/initial-road/
 8  *
 9  */
10 @SuppressWarnings("unchecked")
11 public class Client {
12     
13     public static void main(String[] args) {
14         
15         // 首先产生了一个根节点
16         IRoot ceo = new Root("张三", "总经理", 100000);
17         
18         // 差生三个部门经理,也就是树枝节点
19         IBranch developDep = new Branch("李四", "研发部门经理", 10000);
20         IBranch salesDep = new Branch("王五", "销售部门经理", 20000);
21         IBranch financeDep = new Branch("赵六", "财务部门经理", 30000);
22         
23         // 再把三个小组长产生出来
24         IBranch firstDevGroup = new Branch("杨三", "开发一组组长", 5000);
25         IBranch secondDevGroup = new Branch("吴大", "开发二组组长", 6000);
26         
27         // 剩下的就是我们这些小兵了,就是路人甲,路人乙
28         ILeaf a = new Leaf("a", "开发人员", 2000);
29         ILeaf b = new Leaf("b", "开发人员", 2000);
30         ILeaf c = new Leaf("c", "开发人员", 2000);
31         ILeaf d = new Leaf("d", "开发人员", 2000);
32         ILeaf e = new Leaf("e", "开发人员", 2000);
33         ILeaf f = new Leaf("f", "开发人员", 2000);
34         ILeaf g = new Leaf("g", "开发人员", 2000);
35         ILeaf h = new Leaf("h", "开发人员", 5000);
36         ILeaf i = new Leaf("i", "开发人员", 4000);
37         ILeaf j = new Leaf("j", "开发人员", 5000);
38         ILeaf k = new Leaf("k", "开发人员", 8000);
39         ILeaf zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
40         
41         // 该产生的人都产生出来了,然后我们怎么组装这棵树
42         // 首先是定义总经理下有三个部门经理
43         ceo.add(developDep);
44         ceo.add(salesDep);
45         ceo.add(financeDep);
46         //总经理下还有一个秘书
47         ceo.add(k);
48         
49         // 定义研发部门下的结构
50         developDep.add(firstDevGroup);
51         developDep.add(secondDevGroup);
52         
53         // 研发部经理下还有一个副总
54         developDep.add(zhengLaoLiu);
55         
56         // 看看开发两个小组下有什么
57         firstDevGroup.add(a);
58         firstDevGroup.add(b);
59         firstDevGroup.add(c);
60         
61         secondDevGroup.add(d);
62         secondDevGroup.add(e);
63         secondDevGroup.add(f);
64         secondDevGroup.add(g);
65         
66         // 再看销售部下的人员情况
67         salesDep.add(h);
68         salesDep.add(i);
69         
70         // 最后一个财务
71         financeDep.add(j);
72         
73         // 树状结构写完毕,然后我们打印出来
74         System.out.println(ceo.getInfo());
75         
76         // 打印出来整个树形
77         getAllSubordinateInfo(ceo.getSubordinateInfo());
78     }
79     
80     private static void getAllSubordinateInfo(ArrayList subordinateList){
81         int length = subordinateList.size();
82         for(int m=0;m<length;m++){
83             Object s = subordinateList.get(m);
84             if(s instanceof Leaf){
85                 // 是个叶子节点,也就是员工
86                 ILeaf employee = (ILeaf) s;
87              System.out.println(employee.getInfo());
88             }else{
89                 IBranch branch = (IBranch) s;
90               System.out.println(branch.getInfo());
91                  // 在递归调用
92               getAllSubordinateInfo(branch.getSubordinateInfo());
93             }
94         }
95     }
96 }

  这个程序比较长,如果是在我们的项目中有这样的程序,肯定是被拉出来做典型的,你写一大坨的程序给谁呀,以后还要维护的,程序是要短小精悍!幸运的是,我们是这为案例来讲解,而且就是指出这样组装这棵树是有问题,等会我们深入讲解。

  和我们期望要的结果一样,一棵完整的树就生成了,而且我们还能够遍历。看类图或程序的时候,你有没有发觉有问题?getInfo 每个接口都有为什么不能抽象出来?Root 类和Branch 类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,你是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?——彻底晕菜了!

  问题很多,我们一个一个解决,先说抽象的问题,确实可以吧 IBranch 和 IRoot 合并成一个接口,这个我们先肯定下来,这是个比较大的改动,我们先画个类图:

  这个类图还是有点问题的,接口的作用是什么?定义共性,那 ILeaf 和 IBranch 是不是也有共性呢?有 getInfo(),我们是不是要把这个共性也已经封装起来呢?好,我们再修改一下类图:

  类图上有两个接口,ICorp 是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字,职位,薪水,这个定义成一个接口没有错,IBranch 有没有必要呢?我们先实现出来然后再说。

  先看 ICorp.java 源代码:

 1 package com.pattern.composite.advance;
 2 
 3 /**
 4  * 公司类,定义每个员工都有信息
 5  * @author http://www.cnblogs.com/initial-road/
 6  *
 7  */
 8 public interface ICorp {
 9     
10     // 每个员工都有信息,你想隐藏,门儿都没有!
11     public String getInfo();
12     
13 }
14 
15 // 接口很简单,只有一个方法,就是获得员工的信息,我们再来看实现类:
16 
17 package com.pattern.composite.advance;
18 
19 /**
20  * Leaf是树枝节点,在这里就是我们这些小兵
21  * @author http://www.cnblogs.com/initial-road/
22  *
23  */
24 public class Leaf implements ICorp {
25     
26     // 小兵也有名称
27     private String name = "";
28     
29     // 小兵也有职位
30     private String position = "";
31     
32     // 小兵也有薪水,否则谁给你干
33     private int salary = 0;
34     
35     // 通过一个构造函数传递小兵的信息
36     public Leaf(String name, String position, int salary) {
37         this.name = name;
38         this.position = position;
39         this.salary = salary;
40     }
41     
42     // 获得小兵的信息
43     public String getInfo() {
44         String info = "";
45         info = "姓名:" + this.name;
46         info = info + "\t职位:" + this.position;
47         info = info + "\t薪水:" + this.salary;
48         return info;
49     }
50 
51 }

  小兵就只有这些信息了,我们是具体干活的,我们是管理不了其他同事的,我们来看看那些经理和小组长是怎么实现的,先看接口:

  1 package com.pattern.composite.advance;
  2 
  3 import java.util.ArrayList;
  4 
  5 /**
  6  * 这些下边有小兵或者是经理等风云人物
  7  * @author http://www.cnblogs.com/initial-road/
  8  *
  9  */
 10 public interface IBranch {
 11     
 12     // 能够增加小兵(树叶节点)或者是经过(树枝节点)
 13     public void addSubordinate(ICorp corp);
 14     
 15     // 还要能够获得下属的信息
 16     public ArrayList<ICorp> getSubordinate();
 17     
 18     /**
 19      * 本来还应该又一个方法 delSubordinate(ICorp corp),删除下属
 20      * 这个方法我们没有用到就不写进来了
 21      */
 22 }
 23 
 24 // 接口也是很简单的,下面是实现类:
 25 
 26 package com.pattern.composite.advance;
 27 
 28 import java.util.ArrayList;
 29 
 30 /**
 31  * 这些树枝节点也就是这些领导们既要自己的信息,还要知道自己的下属情况
 32  * @author http://www.cnblogs.com/initial-road/
 33  *
 34  */
 35 public class Branch implements IBranch, ICorp{
 36     // 领导也是人,也有名字
 37     private String name = "";
 38     
 39     // 领导和领导不同,也是职位区别
 40     private String position = "";
 41     
 42     // 领导也是拿薪水的
 43     private int salary = 0;
 44     
 45     // 领导下边有那些下级领导和小兵
 46     ArrayList<ICorp> subordinateList = new ArrayList<ICorp>();
 47 
 48     // 通过构造函数传递领导的信息
 49     public Branch(String name, String position, int salary) {
 50         this.name = name;
 51         this.position = position;
 52         this.salary = salary;
 53     }
 54 
 55     // 增加一个下属,可能是小头目,也可能是个小兵
 56     public void addSubordinate(ICorp corp) {
 57         this.subordinateList.add(corp);
 58     }
 59 
 60     // 我有哪些下属
 61     public ArrayList<ICorp> getSubordinate() {
 62         return this.subordinateList;
 63     }
 64 
 65     // 领导也是人,他也有信息
 66     public String getInfo() {
 67         String info = "";
 68         info = "姓名:" + this.name;
 69         info = info + "\t职位:" + this.position;
 70         info = info + "\t薪水:" + this.salary;
 71         return info;
 72     }
 73 }
 74 
 75 // 实现类也很简单,不多说,程序写的好不好,就看别人怎么调用了,我们看Client.java 程序:
 76 
 77 package com.pattern.composite.advance;
 78 
 79 import java.util.ArrayList;
 80 
 81 /**
 82  * 组装这个树形结构,并展示出来
 83  * @author http://www.cnblogs.com/initial-road/
 84  *
 85  */
 86 @SuppressWarnings("all")
 87 public class Client {
 88     
 89     public static void main(String[] args) {
 90         
 91         // 首先是组装一个组织结构出来
 92         Branch ceo = compositeCorpTree();
 93         
 94         // 首先把CEO的信息打印出来
 95         System.out.println(ceo.getInfo());
 96         
 97         // 然后是所有员工信息
 98         System.out.println(getTreeInfo(ceo));
 99     }
100     
101     // 把整个树组装出来
102     public static Branch compositeCorpTree(){
103         // 首先产生总经理CEO
104         Branch root = new Branch("张三", "总经理", 10000);
105         
106         // 把三个部门经理产生出来
107         Branch developDep = new Branch("李四", "研发部门经理", 10000);
108         Branch salesDep = new Branch("王五", "销售部门经理", 20000);
109         Branch financeDep = new Branch("赵六", "财务部经理", 30000);
110         
111         // 再把三个小组长产生出来
112         Branch firstDevGroup = new Branch("周七", "开发一组组长", 5000);
113         Branch secondDevGroup = new Branch("杨三", "开发二组组长", 6000);
114         
115         // 把所有的小兵都产生出来
116         Leaf a = new Leaf("a", "开发人员", 2000);
117         Leaf b = new Leaf("b", "开发人员", 2000);
118         Leaf c = new Leaf("c", "开发人员", 2000);
119         Leaf d = new Leaf("d", "开发人员", 2000);
120         Leaf e = new Leaf("e", "开发人员", 2000);
121         Leaf f = new Leaf("f", "开发人员", 2000);
122         Leaf g = new Leaf("g", "开发人员", 2000);
123         Leaf h = new Leaf("h", "销售人员", 5000);
124         Leaf i = new Leaf("i", "销售人员", 4000);
125         Leaf j = new Leaf("j", "财务人员", 5000);
126         Leaf k = new Leaf("k", "CEO秘书", 8000);
127         Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副经理", 20000);
128         
129         // 开始组装
130         // CEO下有三个部门经理和一个秘书
131         root.addSubordinate(k);
132         root.addSubordinate(developDep);
133         root.addSubordinate(salesDep);
134         root.addSubordinate(financeDep);
135         
136         // 研发部经理
137         developDep.addSubordinate(zhengLaoLiu);
138         developDep.addSubordinate(firstDevGroup);
139         developDep.addSubordinate(secondDevGroup);
140         
141         // 看看开发两个小组下有什么
142         firstDevGroup.addSubordinate(a);
143         firstDevGroup.addSubordinate(b);
144         firstDevGroup.addSubordinate(c);
145         secondDevGroup.addSubordinate(d);
146         secondDevGroup.addSubordinate(e);
147         secondDevGroup.addSubordinate(f);
148         
149         // 再看销售部下的人员情况
150         salesDep.addSubordinate(h);
151         salesDep.addSubordinate(i);
152         
153         // 最后一个财务
154         financeDep.addSubordinate(j);
155         
156         return root;
157     }
158     
159     // 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
160     public static String getTreeInfo(Branch root){
161         ArrayList<ICorp> subordinate = root.getSubordinate();
162         String info = "";
163         for(ICorp s : subordinate){
164             // 是员工就直接获得信息
165             if(s instanceof Leaf){
166                 info = info + s.getInfo() + "\n";
167             }else{
168                 // 是个小头目
169                 info = info + s.getInfo() + "\n" + getTreeInfo((Branch) s);
170             }
171         }
172         return info;
173     }
174     
175 }

  一个非常清晰的树状人员资源管理图出现了,那我们的程序是否还可以优化?可以! 你看 Leaf 和 Branch中都有 getInfo 信息,是否可以抽象,好,我们抽象一下:

  你一看这个图,乐了,能不乐嘛,减少很多工作量了,接口没有了,改成抽象类了,IBranch 接口也没有了,直接把方法放到了实现类中了,那我们先来看抽象类:

  1 package com.pattern.composite.perfect;
  2 
  3 /**
  4  * 定义一个公司的人员的抽象类
  5  * @author http://www.cnblogs.com/initial-road/
  6  *
  7  */
  8 public abstract class Corp {
  9     
 10     // 公司每个人都有名称
 11     private String name = "";
 12     
 13     // 公司每个人都有职位
 14     private String position = "";
 15     
 16     // 公司每个人都有薪水
 17     private int salary = 0;
 18     
 19     /**
 20      * 通过接口的方式传递,我们改变一下习惯,传递进来的变量名以下划线开始
 21      * 这个在一些开源项目中非常常见,一般构造函数都是这么定义的
 22      */
 23     public Corp(String _name, String _position, int _salary){
 24         this.name = _name;
 25         this.position = _position;
 26         this.salary = _salary;
 27     }
 28     
 29     // 获得员工信息
 30     public String getInfo(){
 31         String info = "";
 32         info = "姓名:" + this.name;
 33         info = info + "\t职位:" + this.position;
 34         info = info + "\t薪水:" + this.salary;
 35         return info;
 36     }
 37 
 38 }
 39 
 40 // 抽象类嘛,就应该抽象出一些共性的东西出来,然后看两个具体的实现类:
 41 
 42 package com.pattern.composite.perfect;
 43 
 44 /**
 45  * 普通员工很简单,就写一个函数就可以了
 46  * @author http://www.cnblogs.com/initial-road/
 47  *
 48  */
 49 public class Leaf extends Corp {
 50 
 51     // 就写一个构造函数,这个是必须的
 52     public Leaf(String name, String position, int salary) {
 53         super(name, position, salary);
 54     }
 55 
 56 }
 57 
 58 // 这个改动比较多,就几行代码就完成了,确实就应该这样,下面是小头目的实现类:
 59 
 60 package com.pattern.composite.perfect;
 61 
 62 import java.util.ArrayList;
 63 
 64 /**
 65  * 节点类,也简单了很多
 66  * @author http://www.cnblogs.com/initial-road/
 67  *
 68  */
 69 public class Branch extends Corp {
 70     
 71     // 领导下边有那些下级领导和小兵
 72     ArrayList<Corp> subordinateList = new ArrayList<Corp>();
 73     
 74     // 构造函数是必须的了
 75     public Branch(String _name, String _position, int _salary) {
 76         super(_name, _position, _salary);
 77     }
 78     
 79     // 增加一个下属,可能是小头目,也可能是个小兵
 80     public void addSubordinate(Corp corp){
 81         this.subordinateList.add(corp);
 82     }
 83     
 84     // 我有哪些下属
 85     public ArrayList<Corp> getSubordinate(){
 86         return this.subordinateList;
 87     }
 88     
 89 }
 90 
 91 // 也缩减了很多,再看 Client.java 程序,这个就没有多大变化了:
 92 
 93 package com.pattern.composite.perfect;
 94 
 95 import java.util.ArrayList;
 96 
 97 /**
 98  * 组装这个树形结构,并展示出来
 99  * @author http://www.cnblogs.com/initial-road/
100  *
101  */
102 public class Client {
103     
104     public static void main(String[] args) {
105         
106         // 首先是组装一个组织结构出来
107         Branch ceo = compositeCorpTree();
108         
109         // 首先把CEO的信息打印出来
110         System.out.println(ceo.getInfo());
111         
112         // 然后是所有员工信息
113         System.out.println(getTreeInfo(ceo));
114     }
115     
116     // 把整个树组装出来
117     public static Branch compositeCorpTree(){
118         // 首先产生总经理CEO
119         Branch root = new Branch("张三", "总经理", 10000);
120         
121         // 把三个部门经理产生出来
122         Branch developDep = new Branch("李四", "研发部门经理", 10000);
123         Branch salesDep = new Branch("王五", "销售部门经理", 20000);
124         Branch financeDep = new Branch("赵六", "财务部经理", 30000);
125         
126         // 再把三个小组产生出来
127         Branch firstDevGroup = new Branch("杨三", "开发一组组长", 5000);
128         Branch secondDevGroup = new Branch("吴大", "开发二组组长", 6000);
129         
130         // 把所有的小兵都产生出来
131         Leaf a = new Leaf("a", "开发人员", 2000);
132         Leaf b = new Leaf("b", "开发人员", 2000);
133         Leaf c = new Leaf("c", "开发人员", 2000);
134         Leaf d = new Leaf("d", "开发人员", 2000);
135         Leaf e = new Leaf("e", "开发人员", 2000);
136         Leaf f = new Leaf("f", "开发人员", 2000);
137         Leaf g = new Leaf("g", "开发人员", 2000);
138         Leaf h = new Leaf("h", "销售人员", 5000);
139         Leaf i = new Leaf("i", "销售人员", 4000);
140         Leaf j = new Leaf("j", "财务人员", 5000);
141         Leaf k = new Leaf("k", "CEO秘书", 8000);
142         Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副总理", 20000);
143         
144         // 开始组装
145         // CEO下有三个部门经理和一个秘书
146         root.addSubordinate(k);
147         root.addSubordinate(developDep);
148         root.addSubordinate(salesDep);
149         root.addSubordinate(financeDep);
150         
151         // 研发部门经理
152         developDep.addSubordinate(zhengLaoLiu);
153         developDep.addSubordinate(firstDevGroup);
154         developDep.addSubordinate(secondDevGroup);
155         
156         // 看看两个开发小组下有什么
157         firstDevGroup.addSubordinate(a);
158         firstDevGroup.addSubordinate(b);
159         firstDevGroup.addSubordinate(c);
160         secondDevGroup.addSubordinate(d);
161         secondDevGroup.addSubordinate(e);
162         secondDevGroup.addSubordinate(f);
163         secondDevGroup.addSubordinate(g);
164         
165         // 再看销售部下的人员情况
166         salesDep.addSubordinate(h);
167         salesDep.addSubordinate(i);
168         
169         // 最后一个财务
170         financeDep.addSubordinate(j);
171         return root;
172     }
173     
174     // 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
175     public static String getTreeInfo(Branch branch){
176         ArrayList<Corp> subordinateList = branch.getSubordinate();
177         String info = "";
178         for(Corp s : subordinateList){
179             if(s instanceof Leaf){
180                 // 是员工就直接获得信息
181                 info = info + s.getInfo() + "\n";
182             }else{
183                 info = info + s.getInfo() + "\n" + getTreeInfo((Branch) s);
184             }
185         }
186         return info;
187     }
188 }

  就是把用到 ICorp 接口的地方修改为 Corp 抽象类就成了,就上面黄色的部分作了点修改,其他保持不变。

  确实是类、接口减少了很多,而且程序也简单很多,但是大家可能还是很迷茫,这个 Client 程序并没有改变多少呀,非常正确,树的组装你是跑不了的,你要知道在项目中使用数据库来存储这些信息的,你从数据库中提出来哪些人要分配到树枝,哪些人要分配到树叶,树枝与树枝、树叶的关系,这些都需要人去定义,通常这里使用一个界面去配置,在数据库中是一个标志信息,例如定义这样一张表:

  从这张表中已经定义个一个树形结构,我们要做的就是从数据库中读取出来,然后展现到前台上,这个读取就用个 for 循环加上递归是不是就可以把一棵树建立起来?我们程序中其实还包涵了数据的读取和加工,用了数据库后,数据和逻辑已经在表中定义好了,我们直接读取放到树上就可以了,这个还是比较容易做了的,大家不妨自己考虑一下。

   上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:

 

   我们先来说说组合模式的几个角色:

  抽象构件角色(Component): 定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的 getInfo 就封装到了抽象类中。

   叶子构件(Leaf):叶子对象,其下再也没有其他的分支。

   树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;

  组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:

  从类图上大家应该能看清楚了,这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中, 比如 add(),remove()以及 getChildren等方法(顺便说一下, getChildren 一般返回的结果为 Iterable的实现类,很多,大家可以看 JDK 的帮助),不管叶子对象还是树枝对象都有相同的结构,通过判断是getChildren 的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题的,不是很建议的方式;安全模式就不同了,它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。

  组合模式的优点有哪些呢?第一个优点只要是树形结构,就要考虑使用组合模式,这个一定记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。组合模式有一个非常明显的缺点,看到我们在 Client.java 中的的定义了树叶和树枝使用时的定义了吗?如下:

  发现什么问题了吗?直接使用了实现类!这个在面向接口编程上是很不恰当的,这个在使用的时候要考虑清楚。

  组合模式在项目中到处都有,比如现在的页面结构一般都是上下结构,上面放系统的 Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形的结构,比较清晰, 这个 JavaScript有很多例子,大家可以到网上搜索一把;还有,我们的自己也是一个树状结构,根据我,能够找到我的父母,根据父亲又能找到爷爷奶奶,根据母亲能够找到外公外婆等等,很典型的树形结构,而且还很规范(这个要是不规范那肯定是乱套了)。

  我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?想想,~~~,再想想!想出来了吧,我们对下答案,先看类图:

  看类图中的红色方框,只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁,我们来看一下程序的改变: 

 

 1 package com.pattern.composite.perfect;
 2 
 3 /**
 4  * 定义一个公司的人员的抽象类
 5  * @author http://www.cnblogs.com/initial-road/
 6  *
 7  */
 8 public abstract class Corp {
 9     
10     // 公司每个人都有名称
11     private String name = "";
12     
13     // 公司每个人都有职位
14     private String position = "";
15     
16     // 公司每个人都有薪水
17     private int salary = 0;
18     
19     // 父节点是谁
20     protected Corp pattern = null;
21     
22     /**
23      * 通过接口的方式传递,我们改变一下习惯,传递进来的变量名以下划线开始
24      * 这个在一些开源项目中非常常见,一般构造函数都是这么定义的
25      */
26     public Corp(String _name, String _position, int _salary){
27         this.name = _name;
28         this.position = _position;
29         this.salary = _salary;
30     }
31     
32     // 获得员工信息
33     public String getInfo(){
34         String info = "";
35         info = "姓名:" + this.name;
36         info = info + "\t职位:" + this.position;
37         info = info + "\t薪水:" + this.salary;
38         return info;
39     }
40 
41     // 得到父节点
42     public Corp getPattern() {
43         return pattern;
44     }
45 
46     // 设置父节点
47     public void setPattern(Corp pattern) {
48         this.pattern = pattern;
49     }
50     
51 }
52 
53 // 就增加了父节点部分,然后我们再来看看 Branch.java 的改变:
54 
55 package com.pattern.composite.perfect;
56 
57 import java.util.ArrayList;
58 
59 /**
60  * 节点类,也简单了很多
61  * @author http://www.cnblogs.com/initial-road/
62  *
63  */
64 public class Branch extends Corp {
65     
66     // 领导下边有那些下级领导和小兵
67     ArrayList<Corp> subordinateList = new ArrayList<Corp>();
68     
69     // 构造函数是必须的了
70     public Branch(String _name, String _position, int _salary) {
71         super(_name, _position, _salary);
72     }
73     
74     // 增加一个下属,可能是小头目,也可能是个小兵
75     public void addSubordinate(Corp corp){
76         // 设置父节点
77         corp.setPattern(this);
78         this.subordinateList.add(corp);
79     }
80     
81     // 我有哪些下属
82     public ArrayList<Corp> getSubordinate(){
83         return this.subordinateList;
84     }
85     
86 }

  增加了父节点部分,看懂程序了吗?就是在每个节点甭管是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后你看整棵树就除了根节点外每个节点都一个父节点,剩下的事情还不好处理吗?每个节点上都有父节点了,你要往上找,那就找呗!Client程序我就不写了,今天已经拷贝的代码实在有点多,大家自己考虑一下,写个 find 方法,然后一个一个往上找,最简单的方法了!

  有了这个 parent 属性,什么后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。

  再提一个问题,树叶节点和树枝节点是有顺序的,你不能乱排的,怎么办?比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的呀,你怎么处理?问我呀,问你呢,好好想想,以后用到着的!

posted @ 2015-01-11 16:24  jay_zhang  阅读(414)  评论(0编辑  收藏  举报