Java设计模式——组合模式(Composite Pattern)

原文链接: http://blog.****.net/u012401711/article/details/52675388


场景一

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

Java设计模式——组合模式(Composite Pattern)

        从最高的老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构(说明一下,这不是二叉树,有关二叉树的定义可以翻翻以前的教科书),我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍,你要确认你建立的树是否有问题呀。
         从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工D 等),我们增加一点学术术语上去,总经理叫做根节点(是不是想到XML 中的那个根节点root,那就对了),类似研发部经理有分支的节点叫做树枝节点,类似员工A 的无分支的节点叫做树叶节点,都很形象,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:

Java设计模式——组合模式(Composite Pattern)

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

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 定义一个根节点,就为总经理服务 
  7. */  
  8. public interface IRoot {  
  9.     //得到总经理的信息  
  10.     public String getInfo();  
  11.     //总经理下边要有小兵,那要能增加小兵,比如研发部总经理,这是个树枝节点  
  12.     public void add(IBranch branch);  
  13.     //那要能增加树叶节点  
  14.     public void add(ILeaf leaf);  
  15.     //既然能增加,那要还要能够遍历,不可能总经理不知道他手下有哪些人  
  16.     public ArrayList getSubordinateInfo();  
  17. }  
这个根节点就是我们的总经理CEO,然后看实现类:

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 根节点的实现类 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Root implements IRoot {  
  10.     //保存根节点下的树枝节点和树叶节点,Subordinate的意思是下级  
  11.     private ArrayList subordinateList = new ArrayList();  
  12.     //根节点的名称  
  13.     private String name = "";  
  14.     //根节点的职位  
  15.     private String position = "";  
  16.     //根节点的薪水  
  17.     private int salary = 0;  
  18.     //通过构造函数传递进来总经理的信息  
  19.     public Root(String name,String position,int salary){  
  20.         this.name = name;  
  21.         this.position = position;  
  22.         this.salary = salary;  
  23.     }  
  24.     //增加树枝节点  
  25.     public void add(IBranch branch) {  
  26.         this.subordinateList.add(branch);  
  27.     }  
  28.     //增加叶子节点,比如秘书,直接隶属于总经理  
  29.     public void add(ILeaf leaf) {  
  30.         this.subordinateList.add(leaf);  
  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.     public ArrayList getSubordinateInfo() {  
  42.         return this.subordinateList;  
  43.     }  
  44. }  
很简单,通过构造函数传入参数,然后获得信息,还可以增加子树枝节点(部门经理)和叶子节点(秘
书)。我们再来看IBranch.java:

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 树枝节点,也就是各个部门经理和组长的角色 
  7. */  
  8. public interface IBranch {  
  9.     //获得信息  
  10.     public String getInfo();  
  11.     //增加数据节点,例如研发部下的研发一组  
  12.     public void add(IBranch branch);  
  13.     //增加叶子节点  
  14.     public void add(ILeaf leaf);  
  15.     //获得下级信息  
  16.     public ArrayList getSubordinateInfo();  
  17. }  
下面是树枝节点的实现类:

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 所有的树枝节点 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Branch implements IBranch {  
  10.     //存储子节点的信息  
  11.     private ArrayList subordinateList = new ArrayList();  
  12.     //树枝节点的名称  
  13.     private String name="";  
  14.     //树枝节点的职位  
  15.     private String position = "";  
  16.     //树枝节点的薪水  
  17.     private int salary = 0;  
  18.     //通过构造函数传递树枝节点的参数  
  19.     public Branch(String name,String position,int salary){  
  20.         this.name = name;  
  21.         this.position = position;  
  22.         this.salary = salary;  
  23.     }  
  24.     //增加一个子树枝节点  
  25.     public void add(IBranch branch) {  
  26.         this.subordinateList.add(branch);  
  27.     }  
  28.     //增加一个叶子节点  
  29.     public void add(ILeaf leaf) {  
  30.         this.subordinateList.add(leaf);  
  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.     public ArrayList getSubordinateInfo() {  
  42.         return this.subordinateList;  
  43.     }  
  44. }  
最后看叶子节点,也就是员工的接口:

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 叶子节点,也就是最小的小兵了,只能自己干活,不能指派别人了 
  6. */  
  7. public interface ILeaf {  
  8.     //获得自己的信息呀  
  9.     public String getInfo();  
  10. }  
下面是叶子节点的实现类:
[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 最小的叶子节点 
  6. */  
  7. @SuppressWarnings("all")  
  8. public class Leaf implements ILeaf {  
  9.     //叶子叫什么名字  
  10.     private String name = "";  
  11.     //叶子的职位  
  12.     private String position = "";  
  13.     //叶子的薪水  
  14.     private int salary=0;  
  15.     //通过构造函数传递信息  
  16.     public Leaf(String name,String position,int salary){  
  17.         this.name = name;  
  18.         this.position = position;  
  19.         this.salary = salary;  
  20.     }  
  21.     //最小的小兵只能获得自己的信息了  
  22.     public String getInfo() {  
  23.         String info = "";  
  24.         info = "名称:" + this.name;  
  25.         info = info + "\t职位:"this.position;  
  26.         info = info + "\t薪水:"+this.salary;  
  27.         return info;  
  28.     }  
  29. }  
好了,所有的根节点,树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现了,然后的工作就是组装成一个树状结构和遍历这个树状结构,看Client.java 程序:

[java] view plain copy
 print?
  1. package com.gumx.common;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * Client的作用是组装这棵树,并遍历一遍 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Client {  
  10.     public static void main(String[] args) {  
  11.         //首先产生了一个根节点  
  12.         IRoot ceo = new Root("王大麻子","总经理",100000);  
  13.         //产生三个部门经理,也就是树枝节点  
  14.         IBranch developDep = new Branch("刘大瘸子","研发部门经理",10000);  
  15.         IBranch salesDep = new Branch("马二拐子","销售部门经理",20000);  
  16.         IBranch financeDep = new Branch("赵三驼子","财务部经理",30000);  
  17.         //再把三个小组长产生出来  
  18.         IBranch firstDevGroup = new Branch("杨三乜斜","开发一组组长",5000);  
  19.         IBranch secondDevGroup = new Branch("吴大棒槌","开发二组组长",6000);  
  20.         //剩下的及时我们这些小兵了,就是路人甲,路人乙  
  21.         ILeaf a = new Leaf("a","开发人员",2000);  
  22.         ILeaf b = new Leaf("b","开发人员",2000);  
  23.         ILeaf c = new Leaf("c","开发人员",2000);  
  24.         ILeaf d = new Leaf("d","开发人员",2000);  
  25.         ILeaf e = new Leaf("e","开发人员",2000);  
  26.         ILeaf f = new Leaf("f","开发人员",2000);  
  27.         ILeaf g = new Leaf("g","开发人员",2000);  
  28.         ILeaf h = new Leaf("h","销售人员",5000);  
  29.         ILeaf i = new Leaf("i","销售人员",4000);  
  30.         ILeaf j = new Leaf("j","财务人员",5000);  
  31.         ILeaf k = new Leaf("k","CEO秘书",8000);  
  32.         ILeaf zhengLaoLiu = new Leaf("郑老六","研发部副总",20000);  
  33.         //该产生的人都产生出来了,然后我们怎么组装这棵树  
  34.         //首先是定义总经理下有三个部门经理  
  35.         ceo.add(developDep);  
  36.         ceo.add(salesDep);  
  37.         ceo.add(financeDep);  
  38.         //总经理下还有一个秘书  
  39.         ceo.add(k);  
  40.         //定义研发部门下的结构  
  41.         developDep.add(firstDevGroup);  
  42.         developDep.add(secondDevGroup);  
  43.         //研发部经理下还有一个副总  
  44.         developDep.add(zhengLaoLiu);  
  45.         //看看开发两个开发小组下有什么  
  46.         firstDevGroup.add(a);  
  47.         firstDevGroup.add(b);  
  48.         firstDevGroup.add(c);  
  49.         secondDevGroup.add(d);  
  50.         secondDevGroup.add(e);  
  51.         secondDevGroup.add(f);  
  52.         //再看销售部下的人员情况  
  53.         salesDep.add(h);  
  54.         salesDep.add(i);  
  55.         //最后一个财务  
  56.         financeDep.add(j);  
  57.         //树状结构写完毕,然后我们打印出来  
  58.         System.out.println(ceo.getInfo());  
  59.         //打印出来整个树形  
  60.         getAllSubordinateInfo(ceo.getSubordinateInfo());  
  61.     }  
  62.     //遍历所有的树枝节点,打印出信息  
  63.     private static void getAllSubordinateInfo(ArrayList subordinateList){  
  64.         int length = subordinateList.size();  
  65.         for(int m=0;m<length;m++){ //定义一个ArrayList长度,不要在for循环中每次计算  
  66.             Object s = subordinateList.get(m);  
  67.             if(s instanceof Leaf){ //是个叶子节点,也就是员工  
  68.                 ILeaf employee = (ILeaf)s;  
  69.                 System.out.println(((Leaf) s).getInfo());  
  70.             }else{  
  71.                 IBranch branch = (IBranch)s;  
  72.                 System.out.println(branch.getInfo());  
  73.                 //再递归调用  
  74.                 getAllSubordinateInfo(branch.getSubordinateInfo());  
  75.             }  
  76.         }  
  77.     }  
  78. }  
这个程序比较长,如果是在我们的项目中有这样的程序,肯定是被拉出来做典型的,你写一大坨的程序给谁呀,以后还要维护的,程序是要短小精悍!幸运的是,我们是这为案例来讲解,而且就是指出这样组装这棵树是有问题,等会我们深入讲解,先看运行结果:

名称:王大麻子职位:总经理薪水: 100000
名称:刘大瘸子职位:研发部门经理薪水:10000
名称:杨三乜斜职位:开发一组组长薪水:5000
名称:a 职位:开发人员薪水:2000
名称:b 职位:开发人员薪水:2000
名称:c 职位:开发人员薪水:2000
名称:吴大棒槌职位:开发二组组长薪水:6000
名称:d 职位:开发人员薪水:2000
名称:e 职位:开发人员薪水:2000
名称:f 职位:开发人员薪水:2000
名称:郑老六职位:研发部副总薪水:20000名称:马二拐子职位:销售部门经理薪水:20000
名称:h 职位:销售人员薪水:5000
名称:i 职位:销售人员薪水:4000
名称:赵三驼子职位:财务部经理薪水:30000
名称:j 职位:财务人员薪水:5000
名称:k 职位:CEO秘书薪水:8000

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

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

Java设计模式——组合模式(Composite Pattern)

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

Java设计模式——组合模式(Composite Pattern)

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

[java] view plain copy
 print?
  1. package com.gumx.advance;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 公司类,定义每个员工都有信息 
  6. */  
  7. public interface ICorp {  
  8.     //每个员工都有信息,你想隐藏,门儿都没有!  
  9.     public String getInfo();  
  10. }  
接口很简单,只有一个方法,就是获得员工的信息,我们再来看实现类:

[java] view plain copy
 print?
  1. package com.gumx.advance;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * Leaf是树叶节点,在这里就是我们这些小兵 
  6. */  
  7. @SuppressWarnings("all")  
  8. public class Leaf implements ICorp {  
  9.     //小兵也有名称  
  10.     private String name = "";  
  11.     //小兵也有职位  
  12.     private String position = "";  
  13.     //小兵也有薪水,否则谁给你干  
  14.     private int salary = 0;  
  15.     //通过一个构造函数传递小兵的信息  
  16.     public Leaf(String name,String position,int salary){  
  17.         this.name = name;  
  18.         this.position = position;  
  19.         this.salary = salary;  
  20.     }  
  21.     //获得小兵的信息  
  22.     public String getInfo() {  
  23.         String info = "";  
  24.         info = "姓名:" + this.name;  
  25.         info = info + "\t职位:"this.position;  
  26.         info = info + "\t薪水:" + this.salary;  
  27.         return info;  
  28.     }  
  29. }  
小兵就只有这些信息了,我们是具体干活的,我们是管理不了其他同事的,我们来看看那些经理和小
组长是怎么实现的,先看接口:

[java] view plain copy
 print?
  1. package com.gumx.advance;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 这些下边有小兵或者是经理等风云人物 
  7. */  
  8. public interface IBranch {  
  9.     //能够增加小兵(树叶节点)或者是经理(树枝节点)  
  10.     public void addSubordinate(ICorp corp);  
  11.     //我还要能够获得下属的信息  
  12.     public ArrayList<ICorp> getSubordinate();  
  13.     /*本来还应该有一个方法delSubordinate(ICorp corp),删除下属 
  14.     * 这个方法我们没有用到就不写进来了 
  15.     */  
  16. }  
接口也是很简单的,下面是实现类:

[java] view plain copy
 print?
  1. package com.gumx.advance;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 这些树枝节点也就是这些领导们既要有自己的信息,还要知道自己的下属情况 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Branch implements IBranch, ICorp {  
  10.     //领导也是人,也有名字  
  11.     private String name = "";  
  12.     //领导和领导不同,也是职位区别  
  13.     private String position = "";  
  14.     //领导也是拿薪水的  
  15.     private int salary = 0;  
  16.     //领导下边有那些下级领导和小兵  
  17.     ArrayList<ICorp> subordinateList = new ArrayList<ICorp>();  
  18.     //通过构造函数传递领导的信息  
  19.     public Branch(String name,String position,int salary){  
  20.         this.name = name;  
  21.         this.position = position;  
  22.         this.salary = salary;  
  23.     }  
  24.     //增加一个下属,可能是小头目,也可能是个小兵  
  25.     public void addSubordinate(ICorp corp) {  
  26.         this.subordinateList.add(corp);  
  27.     }  
  28.     //我有哪些下属  
  29.     public ArrayList<ICorp> getSubordinate() {  
  30.         return this.subordinateList;  
  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. }  
实现类也很简单,不多说,程序写的好不好,就看别人怎么调用了,我们看Client.java 程序:
[java] view plain copy
 print?
  1. package com.gumx.advance;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 组装这个树形结构,并展示出来 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Client {  
  10.     public static void main(String[] args) {  
  11.         //首先是组装一个组织结构出来  
  12.         Branch ceo = compositeCorpTree();  
  13.         //首先把CEO的信息打印出来:  
  14.         System.out.println(ceo.getInfo());  
  15.         //然后是所有员工信息  
  16.         System.out.println(getTreeInfo(ceo));  
  17.     }  
  18.     //把整个树组装出来  
  19.     public static Branch compositeCorpTree(){  
  20.         //首先产生总经理CEO  
  21.         Branch root = new Branch("王大麻子","总经理",100000);  
  22.         //把三个部门经理产生出来  
  23.         Branch developDep = new Branch("刘大瘸子","研发部门经理",10000);  
  24.         Branch salesDep = new Branch("马二拐子","销售部门经理",20000);  
  25.         Branch financeDep = new Branch("赵三驼子","财务部经理",30000);  
  26.         //再把三个小组长产生出来  
  27.         Branch firstDevGroup = new Branch("杨三乜斜","开发一组组长",5000);  
  28.         Branch secondDevGroup = new Branch("吴大棒槌","开发二组组长",6000);  
  29.         //把所有的小兵都产生出来  
  30.         Leaf a = new Leaf("a","开发人员",2000);  
  31.         Leaf b = new Leaf("b","开发人员",2000);  
  32.         Leaf c = new Leaf("c","开发人员",2000);  
  33.         Leaf d = new Leaf("d","开发人员",2000);  
  34.         Leaf e = new Leaf("e","开发人员",2000);  
  35.         Leaf f = new Leaf("f","开发人员",2000);  
  36.         Leaf g = new Leaf("g","开发人员",2000);  
  37.         Leaf h = new Leaf("h","销售人员",5000);  
  38.         Leaf i = new Leaf("i","销售人员",4000);  
  39.         Leaf j = new Leaf("j","财务人员",5000);  
  40.         Leaf k = new Leaf("k","CEO秘书",8000);  
  41.         Leaf zhengLaoLiu = new Leaf("郑老六","研发部副经理",20000);  
  42.         //开始组装  
  43.         //CEO下有三个部门经理和一个秘书  
  44.         root.addSubordinate(k);  
  45.         root.addSubordinate(developDep);  
  46.         root.addSubordinate(salesDep);  
  47.         root.addSubordinate(financeDep);  
  48.         //研发部经理  
  49.         developDep.addSubordinate(zhengLaoLiu);  
  50.         developDep.addSubordinate(firstDevGroup);  
  51.         developDep.addSubordinate(secondDevGroup);  
  52.         //看看开发两个开发小组下有什么  
  53.         firstDevGroup.addSubordinate(a);  
  54.         firstDevGroup.addSubordinate(b);  
  55.         firstDevGroup.addSubordinate(c);  
  56.         secondDevGroup.addSubordinate(d);  
  57.         secondDevGroup.addSubordinate(e);  
  58.         secondDevGroup.addSubordinate(f);  
  59.         //再看销售部下的人员情况  
  60.         salesDep.addSubordinate(h);  
  61.         salesDep.addSubordinate(i);  
  62.         //最后一个财务  
  63.         financeDep.addSubordinate(j);  
  64.         return root;  
  65.     }  
  66.     //遍历整棵树,只要给我根节点,我就能遍历出所有的节点  
  67.     public static String getTreeInfo(Branch root){  
  68.         ArrayList<ICorp> subordinateList = root.getSubordinate();  
  69.         String info = "";  
  70.         for(ICorp s :subordinateList){  
  71.             if(s instanceof Leaf){ //是员工就直接获得信息  
  72.                 info = info + s.getInfo()+"\n";  
  73.             }else//是个小头目  
  74.                 info = info + s.getInfo() +"\n"+ getTreeInfo((Branch)s);  
  75.             }  
  76.         }  
  77.         return info;  
  78.     }  
  79. }  
运行结果如下:
姓名:王大麻子职位:总经理薪水:100000
姓名:k 职位:CEO秘书薪水:8000
姓名:刘大瘸子职位:研发部门经理薪水:10000
姓名:郑老六职位:研发部副经理薪水:20000
姓名:杨三乜斜职位:开发一组组长薪水:5000
姓名:a 职位:开发人员薪水:2000姓名:b 职位:开发人员薪水:2000
姓名:c 职位:开发人员薪水:2000
姓名:吴大棒槌职位:开发二组组长薪水:6000
姓名:d 职位:开发人员薪水:2000
姓名:e 职位:开发人员薪水:2000
姓名:f 职位:开发人员薪水:2000
姓名:马二拐子职位:销售部门经理薪水:20000
姓名:h 职位:销售人员薪水:5000
姓名:i 职位:销售人员薪水:4000
姓名:赵三驼子职位:财务部经理薪水:30000
姓名:j 职位:财务人员薪水:5000

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

Java设计模式——组合模式(Composite Pattern)

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

[java] view plain copy
 print?
  1. package com.gumx.perfect;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 定义一个公司的人员的抽象类 
  6. */  
  7. @SuppressWarnings("all")  
  8. public abstract class Corp {  
  9.     //公司每个人都有名称  
  10.     private String name = "";  
  11.     //公司每个人都职位  
  12.     private String position = "";  
  13.     //公司每个人都有薪水  
  14.     private int salary =0;  
  15.     /*通过接口的方式传递,我们改变一下习惯,传递进来的参数名以下划线开始 
  16.     * 这个在一些开源项目中非常常见,一般构造函数都是这么定义的 
  17.     */  
  18.     public Corp(String _name,String _position,int _salary){  
  19.         this.name = _name;  
  20.         this.position = _position;  
  21.         this.salary = _salary;  
  22.     }  
  23.     //获得员工信息  
  24.     public String getInfo(){  
  25.         String info = "";  
  26.         info = "姓名:" + this.name;  
  27.         info = info + "\t职位:"this.position;  
  28.         info = info + "\t薪水:" + this.salary;  
  29.         return info;  
  30.     }  
  31. }  
抽象类嘛,就应该抽象出一些共性的东西出来,然后看两个具体的实现类:

[java] view plain copy
 print?
  1. package com.gumx.perfect;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 普通员工很简单,就写一个构造函数就可以了 
  6. */  
  7. public class Leaf extends Corp {  
  8.     //就写一个构造函数,这个是必须的  
  9.     public Leaf(String _name,String _position,int _salary){  
  10.         super(_name,_position,_salary);  
  11.     }  
  12. }  
这个改动比较多,就几行代码就完成了,确实就应该这样,下面是小头目的实现类:
[java] view plain copy
 print?
  1. package com.gumx.perfect;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 节点类,也简单了很多 
  7. */  
  8. public class Branch extends Corp {  
  9.     //领导下边有那些下级领导和小兵  
  10.     ArrayList<Corp> subordinateList = new ArrayList<Corp>();  
  11.     //构造函数是必须的了  
  12.     public Branch(String _name,String _position,int _salary){  
  13.         super(_name,_position,_salary);  
  14.     }  
  15.     //增加一个下属,可能是小头目,也可能是个小兵  
  16.     public void addSubordinate(Corp corp) {  
  17.         this.subordinateList.add(corp);  
  18.     }  
  19.     //我有哪些下属  
  20.     public ArrayList<Corp> getSubordinate() {  
  21.         return this.subordinateList;  
  22.     }  
  23. }  
也缩减了很多,再看Client.java 程序,这个就没有多大变化了:
[java] view plain copy
 print?
  1. package com.gumx.perfect;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 组装这个树形结构,并展示出来 
  7. */  
  8. @SuppressWarnings("all")  
  9. public class Client {  
  10.     public static void main(String[] args) {  
  11.         //首先是组装一个组织结构出来  
  12.         Branch ceo = compositeCorpTree();  
  13.         //首先把CEO的信息打印出来:  
  14.         System.out.println(ceo.getInfo());  
  15.         //然后是所有员工信息  
  16.         System.out.println(getTreeInfo(ceo));  
  17.     }  
  18.     //把整个树组装出来  
  19.     public static Branch compositeCorpTree(){  
  20.         //首先产生总经理CEO  
  21.         Branch root = new Branch("王大麻子","总经理",100000);  
  22.         //把三个部门经理产生出来  
  23.         Branch developDep = new Branch("刘大瘸子","研发部门经理",10000);  
  24.         Branch salesDep = new Branch("马二拐子","销售部门经理",20000);  
  25.         Branch financeDep = new Branch("赵三驼子","财务部经理",30000);  
  26.         //再把三个小组长产生出来  
  27.         Branch firstDevGroup = new Branch("杨三乜斜","开发一组组长",5000);  
  28.         Branch secondDevGroup = new Branch("吴大棒槌","开发二组组长",6000);  
  29.         //把所有的小兵都产生出来  
  30.         Leaf a = new Leaf("a","开发人员",2000);  
  31.         Leaf b = new Leaf("b","开发人员",2000);  
  32.         Leaf c = new Leaf("c","开发人员",2000);  
  33.         Leaf d = new Leaf("d","开发人员",2000);  
  34.         Leaf e = new Leaf("e","开发人员",2000);  
  35.         Leaf f = new Leaf("f","开发人员",2000);  
  36.         Leaf g = new Leaf("g","开发人员",2000);  
  37.         Leaf h = new Leaf("h","销售人员",5000);  
  38.         Leaf i = new Leaf("i","销售人员",4000);  
  39.         Leaf j = new Leaf("j","财务人员",5000);  
  40.         Leaf k = new Leaf("k","CEO秘书",8000);  
  41.         Leaf zhengLaoLiu = new Leaf("郑老六","研发部副经理",20000);  
  42.         //开始组装  
  43.         //CEO下有三个部门经理和一个秘书  
  44.         root.addSubordinate(k);  
  45.         root.addSubordinate(developDep);  
  46.         root.addSubordinate(salesDep);  
  47.         root.addSubordinate(financeDep);  
  48.         //研发部经理  
  49.         developDep.addSubordinate(zhengLaoLiu);  
  50.         developDep.addSubordinate(firstDevGroup);  
  51.         developDep.addSubordinate(secondDevGroup);  
  52.         //看看开发两个开发小组下有什么  
  53.         firstDevGroup.addSubordinate(a);  
  54.         firstDevGroup.addSubordinate(b);  
  55.         firstDevGroup.addSubordinate(c);  
  56.         secondDevGroup.addSubordinate(d);  
  57.         secondDevGroup.addSubordinate(e);  
  58.         secondDevGroup.addSubordinate(f);  
  59.         //再看销售部下的人员情况  
  60.         salesDep.addSubordinate(h);  
  61.         salesDep.addSubordinate(i);  
  62.         //最后一个财务  
  63.         financeDep.addSubordinate(j);  
  64.         return root;  
  65.     }  
  66.     //遍历整棵树,只要给我根节点,我就能遍历出所有的节点  
  67.     public static String getTreeInfo(Branch root){  
  68.         ArrayList<Corp> subordinateList = root.getSubordinate();  
  69.         String info = "";  
  70.         for(Corp s :subordinateList){  
  71.             if(s instanceof Leaf){ //是员工就直接获得信息  
  72.                 info = info + s.getInfo()+"\n";  
  73.             }else//是个小头目  
  74.                 info = info + s.getInfo() +"\n"+ getTreeInfo((Branch)s);  
  75.             }  
  76.          }  
  77.          return info;  
  78.     }  
  79. }  
就是把用到ICorp 接口的地方修改为Corp 抽象类就成了,就上面黄色的部分作了点修改,其他保持不
变,运行结果还是保持一样:

姓名:王大麻子职位:总经理薪水:100000
姓名:k 职位:CEO秘书薪水:8000
姓名:刘大瘸子职位:研发部门经理薪水:10000
姓名:郑老六职位:研发部副经理薪水:20000
姓名:杨三乜斜职位:开发一组组长薪水:5000
姓名:a 职位:开发人员薪水:2000
姓名:b 职位:开发人员薪水:2000
姓名:c 职位:开发人员薪水:2000
姓名:吴大棒槌职位:开发二组组长薪水:6000
姓名:d 职位:开发人员薪水:2000
姓名:e 职位:开发人员薪水:2000
姓名:f 职位:开发人员薪水:2000
姓名:马二拐子职位:销售部门经理薪水:20000
姓名:h 职位:销售人员薪水:5000
姓名:i 职位:销售人员薪水:4000
姓名:赵三驼子职位:财务部经理薪水:30000
姓名:j 职位:财务人员薪水:5000

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

Java设计模式——组合模式(Composite Pattern)

Java设计模式——组合模式(Composite Pattern)

        从这张表中已经定义个一个树形结构,我们要做的就是从数据库中读取出来,然后展现到前台上,这个读取就用个for 循环加上递归是不是就可以把一棵树建立起来?我们程序中其实还包涵了数据的读取和加工,用了数据库后,数据和逻辑已经在表中定义好了,我们直接读取放到树上就可以了,这个还是比较容易做了的,大家不妨自己考虑一下。
        上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:

Java设计模式——组合模式(Composite Pattern)

我们先来说说组合模式的几个角色:
抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;
比如我们例子中的getInfo 就封装到了抽象类中。
叶子构件(Leaf):叶子对象,其下再也没有其他的分支。
树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;
组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:

Java设计模式——组合模式(Composite Pattern)

                                                                      透明模式类图

Java设计模式——组合模式(Composite Pattern)

                                                                               安全模式类图

        从类图上大家应该能看清楚了,这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add(),remove()以及getChildren 等方法(顺便说一下,getChildren 一般返回的结果为Iterable的实现类,很多,大家可以看JDK 的帮助),不管叶子对象还是树枝对象都有相同的结构,通过判断是getChildren 的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题的,不是很建议的方式;安全模式就不同了,它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。组合模式的优点有哪些呢?第一个优点只要是树形结构,就要考虑使用组合模式,这个一定记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。组合模式有一个非常明显的缺点,看到我们在Client.java 中的的定义了树叶和树枝使用时的定义了吗?如下:

[java] view plain copy
 print?
  1. Branch developDep = new Branch("刘大瘸子","研发部门经理",10000);  
  2. …  
  3. Leaf g = new Leaf("g","开发人员",2000);  
        发现什么问题了吗?直接使用了实现类!这个在面向接口编程上是很不恰当的,这个在使用的时候要考虑清楚。
        组合模式在项目中到处都有,比如现在的页面结构一般都是上下结构,上面放系统的Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形的结构,比较清晰,这个JavaScript有很多例子,大家可以到网上搜索一把;还有,我们的自己也是一个树状结构,根据我,能够找到我的父母,根据父亲又能找到爷爷奶奶,根据母亲能够找到外公外婆等等,很典型的树形结构,而且还很规范(这个要是不规范那肯定是乱套了)。

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

Java设计模式——组合模式(Composite Pattern)

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

[java] view plain copy
 print?
  1. package com.gumx.extend;  
  2. /** 
  3. * @author gumx 
  4. * I'm glad to share my knowledge with you all. 
  5. * 定义一个公司的人员的抽象类 
  6. */  
  7. @SuppressWarnings("all")  
  8. public abstract class Corp {  
  9.     //公司每个人都有名称  
  10.     private String name = "";  
  11.     //公司每个人都职位  
  12.     private String position = "";  
  13.     //公司每个人都有薪水  
  14.     private int salary =0;  
  15.     //父节点是谁  
  16. <span style="background-color: rgb(255, 255, 51);">    private Corp parent = null;</span>  
  17.     /*通过接口的方式传递,我们改变一下习惯,传递进来的参数名以下划线开始 
  18.     * 这个在一些开源项目中非常常见,一般构造函数都是定义的 
  19.     */  
  20.     public Corp(String _name,String _position,int _salary){  
  21.         this.name = _name;  
  22.         this.position = _position;  
  23.         this.salary = _salary;  
  24.     }  
  25.     //获得员工信息  
  26.     public String getInfo(){  
  27.         String info = "";  
  28.         info = "姓名:" + this.name;  
  29.         info = info + "\t职位:"this.position;  
  30.         info = info + "\t薪水:" + this.salary;  
  31.         return info;  
  32.     }  
  33. <span style="background-color: rgb(255, 255, 51);">    //设置父节点  
  34.     protected void setParent(Corp _parent){  
  35.         this.parent = _parent;  
  36.     }  
  37.     //得到父节点  
  38.     public Corp getParent(){  
  39.         return this.parent;  
  40.     }</span>  
  41. }  
就增加了黄色部分,然后我们再来看看Branch.java 的改变:
[java] view plain copy
 print?
  1. package com.gumx.extend;  
  2. import java.util.ArrayList;  
  3. /** 
  4. * @author gumx 
  5. * I'm glad to share my knowledge with you all. 
  6. * 节点类,也简单了很多 
  7. */  
  8. public class Branch extends Corp {  
  9.     //领导下边有那些下级领导和小兵  
  10.     ArrayList<Corp> subordinateList = new ArrayList<Corp>();  
  11.     //构造函数是必须的了  
  12.     public Branch(String _name,String _position,int _salary){  
  13.         super(_name,_position,_salary);  
  14.     }  
  15.     //增加一个下属,可能是小头目,也可能是个小兵  
  16.     public void addSubordinate(Corp corp) {  
  17.        <span style="background-color: rgb(255, 255, 51);"> corp.setParent(this); //设置父节点</span>  
  18.         this.subordinateList.add(corp);  
  19.     }  
  20.     //我有哪些下属  
  21.     public ArrayList<Corp> getSubordinate() {  
  22.         return this.subordinateList;  
  23.     }  
  24. }  
        增加了黄色部分,看懂程序了吗?就是在每个节点甭管是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后你看整棵树就除了根节点外每个节点都一个父节点,剩下的事情还不好处理吗?每个节点上都有父节点了,你要往上找,那就找呗!Client程序我就不写了,今天已经拷贝的代码实在有点多,大家自己考虑一下,写个find 方法,然后一个一个往上找,最简单的方法了!
        有了这个parent 属性,什么后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。再提一个问题,树叶节点和树枝节点是有顺序的,你不能乱排的,怎么办?比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的呀,你怎么处理?问我呀,问你呢,好好想想,以后
用到着的!