首页 > 代码库 > 24种设计模式--组合模式【Composite Pattern】
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 // 首先产生总经理CEO104 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 // 首先产生总经理CEO119 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 属性,什么后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。
再提一个问题,树叶节点和树枝节点是有顺序的,你不能乱排的,怎么办?比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的呀,你怎么处理?问我呀,问你呢,好好想想,以后用到着的!
24种设计模式--组合模式【Composite Pattern】