21.1 公司的人事架构是这样的吗
各位读者,大家在上学的时候应该都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,我们上学那会儿这一章节是必考内容,左子树,右子树,什么先序遍历、后序遍历,重点就是二叉树的遍历,我还记得当时老师就说,考试的时候一定有二叉树的构建和遍历,现在想起来还是觉得老师是正确的,树状结构在实际中应用非常广泛,想想看你最常使用的XML格式是不是就是一个树形结构。
咱就先说个最常见的例子,公司的人事管理就是一个典型的树状结构,想想看你公司的组织架构是不是如图21-1所示。
图21-1 普遍的组织架构
从最高的老大,往下一层一层的管理,最后到我们这层小兵……很典型的树状结构(说明一下,这不是二叉树,有关二叉树的定义可以翻翻以前的教科书),我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍,就类似于阅读你公司的人员花名册。
从该树状结构上分析,有两种不同性质的节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工D等),我们增加一点学术术语上去,总经理叫做根节点(是不是想到XML中的那个根节点root,那就对了),类似研发部经理有分支的节点叫做树枝节点,类似员工A的无分支的节点叫做树叶节点,都很形象,三个类型的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图,如图21-2所示。
图21-2 最容易想到的组织架构类图
这个类图是初学者最容易想到的类图(首先声明,这个类图是有缺陷的,如果你已经看明白这个类图的缺陷了,该段落就可以一目十行地看下去,我们是循序渐进地讲课,一步一个脚印),非常简单,我们来看一下如何实现,先看最高级别的根节点接口,如代码清单21-1所示。
代码清单21-1 根节点接口
1 2 3 4 5 6 7 8 9 10
| public interface IRoot { public String getInfo(); public void add(IBranch branch); public void add(ILeaf leaf); public ArrayList getSubordinateInfo(); }
|
这个根节点的对象就是我们的总经理,其具体实现如代码清单21-2所示。
代码清单21-2 根节点的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class Root implements IRoot { private ArrayList subordinateList = new ArrayList(); private String name = ""; private String position = ""; private int salary = 0; public Root(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; } public void add(IBranch branch) { this.subordinateList.add(branch); } public void add(ILeaf leaf) { this.subordinateList.add(leaf); } public String getInfo() { String info = ""; info = "名称:"+ this.name; ; info = info + "\t职位:" + this.position; info = info + "\t薪水: " + this.salary; return info; } public ArrayList getSubordinateInfo() { return this.subordinateList; } }
|
很简单,通过构造函数传入参数,然后获得信息,可以增加子树枝节点(部门经理)和叶子节点(秘书)。我们再来看其他有分支的节点接口,如代码清单21-3所示。
代码清单21-3 其他有分支的节点接口
1 2 3 4 5 6 7 8 9 10
| public interface IBranch { public String getInfo(); public void add(IBranch branch); public void add(ILeaf leaf); public ArrayList getSubordinateInfo(); }
|
有了接口,就应该有实现,其具体的实现类,如代码清单21-4所示。
代码清单21-4 分支的节点实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Branch implements IBranch { private ArrayList subordinateList = new ArrayList(); private String name=""; private String position = ""; private int salary = 0; public Branch(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; } public void add(IBranch branch) { this.subordinateList.add(branch); } public void add(ILeaf leaf) { this.subordinateList.add(leaf); } public String getInfo() { String info = ""; info = "名称:" + this.name; info = info + "\t职位:"+ this.position; info = info + "\t薪水:"+this.salary; return info; } public ArrayList getSubordinateInfo() { return this.subordinateList; } }
|
不管是总经理还是部门经理都是有子节点的存在,最终的子节点就是叶子节点,其接口如代码清单21-5所示。
代码清单21-5 叶子节点的接口
1 2 3 4
| public interface ILeaf { public String getInfo(); }
|
叶子节点的接口简单,实现也非常容易,如代码清单21-6所示。
代码清单21-6 叶子节点的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Leaf implements ILeaf { private String name = ""; private String position = ""; private int salary=0; public Leaf(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; } public String getInfo() { String info = ""; info = "名称:" + this.name; info = info + "\t职位:"+ this.position; info = info + "\t薪水:"+this.salary; return info; } }
|
好了,所有的根节点、树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现,然后的工作就是组装成一个树状结构并遍历这棵树,通过什么来完成呢?通过场景类Client完成,如代码清单21-7所示。
代码清单21-7 场景类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| public class Client { public static void main(String[] args) { IRoot ceo = new Root("王大麻子","总经理",100000); IBranch developDep = new Branch("刘大瘸子","研发部门经理",10000); IBranch salesDep = new Branch("马二拐子","销售部门经理",20000); IBranch financeDep = new Branch("赵三驼子","财务部经理",30000); IBranch firstDevGroup = new Branch("杨三乜斜","开发一组组长",5000); IBranch secondDevGroup = new Branch("吴大棒槌","开发二组组长",6000); ILeaf a = new Leaf("a","开发人员",2000); ILeaf b = new Leaf("b","开发人员",2000); ILeaf c = new Leaf("c","开发人员",2000); ILeaf d = new Leaf("d","开发人员",2000); ILeaf e = new Leaf("e","开发人员",2000); ILeaf f = new Leaf("f","开发人员",2000); ILeaf g = new Leaf("g","开发人员",2000); ILeaf h = new Leaf("h","销售人员",5000); ILeaf i = new Leaf("i","销售人员",4000); ILeaf j = new Leaf("j","财务人员",5000); ILeaf k = new Leaf("k","CEO秘书",8000); ILeaf zhengLaoLiu = new Leaf("郑老六","研发部副总",20000); ceo.add(developDep); ceo.add(salesDep); ceo.add(financeDep); ceo.add(k); developDep.add(firstDevGroup); developDep.add(secondDevGroup); developDep.add(zhengLaoLiu); firstDevGroup.add(a); firstDevGroup.add(b); firstDevGroup.add(c); secondDevGroup.add(d); secondDevGroup.add(e); secondDevGroup.add(f); salesDep.add(h); salesDep.add(i); financeDep.add(j); System.out.println(ceo.getInfo()); getAllSubordinateInfo(ceo.getSubordinateInfo()); } private static void getAllSubordinateInfo(ArrayList subordinateList){ int length = subordinateList.size(); for(int m=0;m<length;m++){ Object s = subordinateList.get(m); if(s instanceof Leaf){ ILeaf employee = (ILeaf)s; System.out.println(((Leaf) s).getInfo()); } else{ IBranch branch = (IBranch)s; System.out.println(branch.getInfo()); getAllSubordinateInfo(branch.getSubordinateInfo()); } } } }
|
这个程序比较长,如果在我们的项目中有这样的程序,肯定是要被拉出来做典型的,你写一大坨的程序给谁呀,以后还要维护,程序要短小精悍!幸运的是,我们这是作为案例来讲解,而且就是指出这样组装这棵树是有问题的,等会我们深入讲解,先看运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 名称:王大麻子 职位:总经理 薪水: 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合并成一个接口,确认无疑的事我们先做,那我们就修改一下类图,如图21-3所示。
仔细看看这个类图,还能不能发现点问题。想想看接口的作用是什么?定义一类事物所具有的共性,那ILeaf和IBranch是不是也有共性呢?有,getInfo方法!我们是不是要把这个共性也封装起来呢?是的,是的,提炼事物的共同点,然后封装之,这是我们作为设计专家的拿手好戏,修改后的类图如图21-4所示。
图21-3 整合根节点和树枝节点后的类图
图21-4 修改后的类图
类图上增加了一个ICorp接口,它是公司所有人员信息的接口类,不管你是经理还是员工,你都有名字、职位、薪水,这个定义成一个接口没有错,但是你可能对于ILeaf接口持怀疑状态,空接口有何意义呀?有意义!它是每个树枝节点的代表,系统扩容的时候你就会发现它是多么“栋梁”。我们先来看新增加的接口ICorp,如代码清单21-8所示。
代码清单21-8 公司人员接口
1 2 3 4
| public interface ICorp { public String getInfo(); }
|
接口很简单,只有一个方法,就是获得员工的信息,树叶节点是最基层的构件,我们先来看看它的接口,空接口,如代码清单21-9所示。
代码清单21-9 树叶接口
1 2
| public interface ILeaf extends ICorp { }
|
树叶接口的实现类,如代码清单21-10所示。
代码清单21-10 树叶接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Leaf implements ILeaf { private String name = ""; private String position = ""; private int salary = 0; public Leaf(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; } public String getInfo() { String info = ""; info = "姓名:" + this.name; info = info + "\t职位:"+ this.position; info = info + "\t薪水:" + this.salary; return info; } }
|
小兵就只有这些信息了,我们是具体干活的,我们是管理不了其他同事的,我们来看看那些经理和小组长是怎么实现的,也就是IBranch接口,如代码清单21-11所示。
代码清单21-11 树枝接口
1 2 3 4 5 6 7 8
| public interface IBranch extends ICorp { public void addSubordinate(ICorp corp); public ArrayList<ICorp> getSubordinate(); }
|
接口也很简单,其实现类也不可能太复杂,如代码清单21-12所示。
代码清单21-12 树枝实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Branch implements IBranch { private String name = ""; private String position = ""; private int salary = 0; ArrayList<ICorp> subordinateList = new ArrayList<ICorp>(); public Branch(String name,String position,int salary){ this.name = name; this.position = position; this.salary = salary; } public void addSubordinate(ICorp corp) { this.subordinateList.add(corp); } public ArrayList<ICorp> getSubordinate() { return this.subordinateList; } public String getInfo() { String info = ""; info = "姓名:" + this.name; info = info + "\t职位:"+ this.position; info = info + "\t薪水:" + this.salary; return info; } }
|
实现类也很简单,不多说,程序写得好不好,就看别人怎么调用了,我们看场景类Client,如代码清单21-13所示。
代码清单21-13 场景类a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public class Client { public static void main(String[] args) { Branch ceo = compositeCorpTree(); System.out.println(ceo.getInfo()); System.out.println(getTreeInfo(ceo)); } public static Branch compositeCorpTree(){ Branch root = new Branch("王大麻子","总经理",100000); Branch developDep = new Branch("刘大瘸子","研发部门经理",10000); Branch salesDep = new Branch("马二拐子","销售部门经理",20000); Branch financeDep = new Branch("赵三驼子","财务部经理",30000); Branch firstDevGroup = new Branch("杨三乜斜","开发一组组长",5000); Branch secondDevGroup = new Branch("吴大棒槌","开发二组组长",6000); Leaf a = new Leaf("a","开发人员",2000); Leaf b = new Leaf("b","开发人员",2000); Leaf c = new Leaf("c","开发人员",2000); Leaf d = new Leaf("d","开发人员",2000); Leaf e = new Leaf("e","开发人员",2000); Leaf f = new Leaf("f","开发人员",2000); Leaf g = new Leaf("g","开发人员",2000); Leaf h = new Leaf("h","销售人员",5000); Leaf i = new Leaf("i","销售人员",4000); Leaf j = new Leaf("j","财务人员",5000); Leaf k = new Leaf("k","CEO秘书",8000); Leaf zhengLaoLiu = new Leaf("郑老六","研发部副经理",20000); root.addSubordinate(k); root.addSubordinate(developDep); root.addSubordinate(salesDep); root.addSubordinate(financeDep); developDep.addSubordinate(zhengLaoLiu); developDep.addSubordinate(firstDevGroup); developDep.addSubordinate(secondDevGroup); firstDevGroup.addSubordinate(a); firstDevGroup.addSubordinate(b); firstDevGroup.addSubordinate(c); secondDevGroup.addSubordinate(d); secondDevGroup.addSubordinate(e); secondDevGroup.addSubordinate(f); salesDep.addSubordinate(h); salesDep.addSubordinate(i); financeDep.addSubordinate(j); return root; } public static String getTreeInfo(Branch root){ ArrayList<ICorp> subordinateList = root.getSubordinate(); String info = ""; for(ICorp s :subordinateList){ if(s instanceof Leaf){ info = info + s.getInfo()+"\n"; } else{ info = info + s.getInfo() +"\n"+ getTreeInfo((Branch)s); } } return info; } }
|
运行结果完全相同,不再赘述。通过这样构件,一个非常清晰的树状人员资源管理图出现了,那我们的程序是否还可以优化?可以!你看Leaf和Branch中都有getInfo信息,是不是可以抽象?好,我们抽象一下,如图21-5所示。
图21-5 精简的类图
你一看这个图,乐了。能不乐嘛,减少很多工作量了,接口没有了,改成抽象类了,IBranch接口也没有了,直接把方法放到了实现类中了,太精简了!而且场景类只认定抽象类Corp就成,那我们首先来看抽象类ICorp,如代码清单21-14所示。
代码清单21-14 抽象公司职员类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public abstract class Corp { private String name = ""; private String position = ""; private int salary =0; public Corp(String _name,String _position,int _salary){ this.name = _name; this.position = _position; this.salary = _salary; } public String getInfo(){ String info = ""; info = "姓名:" + this.name; info = info + "\t职位:"+ this.position; info = info + "\t薪水:" + this.salary; return info; } }
|
抽象类嘛,就应该抽象出一些共性的东西出来,然后看两个具体的实现类,树叶节点如代码清单21-15所示。
代码清单21-15 树叶节点
1 2 3 4 5 6
| public class Leaf extends Corp { public Leaf(String _name,String _position,int _salary){ super(_name,_position,_salary); } }
|
这个精简得比较多,几行代码就完成了,确实就应该这样,下面是小头目的实现类,如代码清单21-16所示。
代码清单21-16 树枝节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Branch extends Corp { ArrayList<Corp> subordinateList = new ArrayList<Corp>(); public Branch(String _name,String _position,int _salary){ super(_name,_position,_salary); } public void addSubordinate(Corp corp) { this.subordinateList.add(corp); } public ArrayList<Corp> getSubordinate() { return this.subordinateList; } }
|
场景类中构建树形结构,并进行遍历。组装没有变化,遍历组织机构数稍有变化,如代码清单21-17所示。
代码清单21-17 稍稍修改的场景类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Client { public static String getTreeInfo(Branch root){ ArrayList<Corp> subordinateList = root.getSubordinate(); String info = ""; for(Corp s :subordinateList){ if(s instanceof Leaf){ info = info + s.getInfo()+"\n"; } else{ info = info+s.getInfo()+"\n"+ getTreeInfo((Branch)s); } } return info; } }
|
场景类中main方法没有变动,请参考代码清单21-7所示,不再赘述。遍历组织机构树的getTreeInfo稍有修改,就是把用到ICorp接口的地方修改为Corp抽象类,仅仅修改了粗体部分,其他保持不变,运行结果相同。这就是组合模式。