上大学的时候大家都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,考试的时候一定有二叉树的构建和遍历,树状结果在实际项目应用的非常广泛。
咱先说这个最常见的例子,公司的人事管理就是一个典型的树状结构,你想想你公司的结构是不是这样:
从最高的老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构(这不是二叉树),我们今天的任务就是把这个树状结构实现出来,并且把它遍历一边,你要确认你建立的树是否有问题啊。
从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工B等),我们增加一点学术术语上去,总经理叫做根节点,类似研发部经理有分支的节点叫做树枝节点,类似员工A的五分只的节点叫做树叶节点,都很形象,三个类型的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:
这个类图是初学者最容易想到的类图,(如果你已经看明白这个类图的缺陷了,我们可以不看下边的实现了),看这个实现:
先看最高级别的根节点的实现:
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 定义一个根节点,为总经理服务
*/
public interface IRoot {
//得到总经理的信息
public String getInfo();
//研发部总经理,这个是树节点
public void add(IBranch branch);
//那要能增加树叶节点
public void add(ILeaf leaf);
//既然能增加,那要还要能遍历,不可能总经理不知道他手下有哪些人
public ArrayList getSubordinateInfo();
}
这个根节点是我们的总经理CEO,然后看实现类:
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 根节点的实现类
*/
public class Root implements IRoot {
//保存根节点下的树枝借点和树叶节点,Subordinate的意思是下级
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;
}
@Override
public String getInfo() {
String info = "";
info = "名称:"+this.name;
info = info+"\t职位:"+this.position;
info = info+"\t薪水"+this.salary;
return info;
}
@Override
public void add(IBranch branch) {
this.subordinateList.add(branch);
}
@Override
public void add(ILeaf leaf) {
this.subordinateList.add(leaf);
}
@Override
public ArrayList getSubordinateInfo() {
return this.subordinateList;
}
}
很简单,通过构造函数传入参数,然后获得信息,还可以增加子树枝节点(部门经理)和叶子节点(秘书),我们再来看IBranch.java
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 树枝节点,也就是各个部门经理和组长的角色
*/
public interface IBranch {
//获得信息
public String getInfo();
//增加数据节点,例如研发部门的研发一组
public void add(IBranch branch);
//增加叶子节点
public void add(ILeaf leaf);
//获得下级信息
public ArrayList getSubordinateInfo();
}
下面是树枝节点的实现类:
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 所有树节点
*/
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;
}
@Override
public String getInfo() {
String info = "";
info = "名称:"+this.name;
info = info+"\t职位:"+this.position;
info = info+"\t薪水"+this.salary;
return info;
}
@Override
public void add(IBranch branch) {
this.subordinateList.add(branch);
}
@Override
public void add(ILeaf leaf) {
this.subordinateList.add(leaf);
}
@Override
public ArrayList getSubordinateInfo() {
return this.subordinateList;
}
}
最后看叶子节点,也就是员工的接口:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 叶子节点,也就是最小的兵了,只能自己干活,不能指派别人
*/
public interface ILeaf {
//获得自己的信息啊
public String getInfo();
}
下面是叶子节点的实现类:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 最小的叶子节点
*/
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;
}
@Override
public String getInfo() {
String info = "";
info = "名称:"+this.name;
info = info+"\t职位:"+this.position;
info = info+"\t薪水"+this.salary;
return info;
}
}
好了,所有根节点,树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现了,然后的工作就是组装成一个树状结构和遍历这个树状结构,看action();
//首先产生一个根节点
IRoot ceo = new Root("王大麻子","总经理",100000);
//产生三个部门经理,也就是树枝节点
IBranch developDep = new Branch("刘大瘸子","研发部经理",10000);
IBranch salesDep = new Branch("马二拐子","销售部经理",20000);
IBranch financeDep = new Branch("赵三驼子","财务部经理",30000);
//再把三个小组长产生出来
IBranch first = new Branch("杨三","开发一组组长",5000);
IBranch two = 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","开发人员",3000);
ILeaf g = new Leaf("g","销售人员",4000);
ILeaf i = new Leaf("i","销售人员",5000);
ILeaf h = new Leaf("h","CEO小秘",8000);
ILeaf j = new Leaf("j","财务人员",3000);
ILeaf zhanggLaoLiu = new Leaf("郑老六","研发部副总",20000);
//该生产出来的人都产生出来了,然后我们怎么组装这棵树
//首先是定义总经理下有三个部门经理
ceo.add(developDep);
ceo.add(salesDep);
ceo.add(financeDep);
//总经理下还有一个秘书
ceo.add(h);
//定义研发部门下的结构
developDep.add(first);
developDep.add(two);
developDep.add(zhanggLaoLiu);
//看看开发两个开发小组有什么
first.add(a);
first.add(b);
first.add(c);
two.add(d);
two.add(e);
two.add(f);
//再看看销售部下的人员情况
salesDep.add(g);
salesDep.add(i);
//最后一个财务
financeDep.add(j);
Log.e("xyz","ceo.getInfo === "+ceo.getInfo());
//打印出来整个树形
getAllSubordinateInfo(ceo.getSubordinateInfo());
}
//遍历所有的树枝节点,打印信息
private static void getAllSubordinateInfo(ArrayList subordinateList){
int length = subordinateList.size();
for (int i = 0; i <length ; i++) {
Object s = subordinateList.get(i);
if (s instanceof Leaf){
ILeaf employee = (ILeaf) s;
Log.e("xyz","leaf.info === "+((Leaf) s).getInfo());
}else{
IBranch branch = (IBranch) s;
Log.e("xyz","branch.getinfo === "+branch.getInfo());
//再递归调用
getAllSubordinateInfo(branch.getSubordinateInfo());
}
}
运行结果如下:
这个程序比较长,如果是在我们的项目中有这样的程序肯定是被拉出来做典型的,我们是为案例来讲解,而且就是指出这样组装这棵树是有问题。虽然结果和我们期望的一样,一颗完整的树生成了,而且我们还能遍历。看类图或程序的时候,你有没有发觉有问题?getInfo每个接口都有为什么不能抽象出来?Root类和Branch类有什么差别?为什么鼎城两个接口两个类?如果我要加一个任职期限,你是不是每给累都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?
问题很多,我们一个一个解决,先说抽象的问题,确实可以把IBranch和IRoot合并成一个接口,这个我们先肯定下来,这个是个比较大的改动,我们先画个类图:
这个类图还是有点问题的,接口的作用是什么?定义共性,那ILeaf和IBranch是不是也有共性呢?有getInfo(),我们是不是要把这个共性也封装起来呢?好,我们再修改一下类图:
类图上有两个接口,ICorp是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字、职位、薪水,这个定义成一个接口没有错,IBranch有咩有必要呢?我们先实现出来然后再说。
先看ICorp.java源代码:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 公司类,定义每个员工都有信息
*/
public interface ICorp {
//每个员工都有信息
public String getInfo();
}
接口很简单,只有一个办法,就是获得员工的信息,我们再来看实现类:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 最小的叶子节点
*/
public class Leaf implements ICorp {
//根节点的名称
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;
}
@Override
public String getInfo() {
String info = "";
info = "名称:"+this.name;
info = info+"\t职位:"+this.position;
info = info+"\t薪水"+this.salary;
return info;
}
}
小兵就只有这些信息了,我们是具体干活的,我们是管理不了其他同事的,经理和小组长的实现类似,就不写了。我们接下来看调用:
//把整个树组装出来
public static BranchChange compositionCropTree(){
//首先产生一个根节点
BranchChange ceo = new BranchChange("王大麻子","总经理",100000);
//产生三个部门经理,也就是树枝节点
BranchChange developDep = new BranchChange("刘大瘸子","研发部经理",10000);
BranchChange salesDep = new BranchChange("马二拐子","销售部经理",20000);
BranchChange financeDep = new BranchChange("赵三驼子","财务部经理",30000);
//再把三个小组长产生出来
BranchChange first = new BranchChange("杨三","开发一组组长",5000);
BranchChange two = new BranchChange("张大棒槌","开发二组组长",6000);
//剩下的就是我们这些小兵了,就死路人甲,路人乙
LeafChange a = new LeafChange("a","开发人员",2000);
LeafChange b = new LeafChange("b","开发人员",2000);
LeafChange c = new LeafChange("c","开发人员",2000);
LeafChange d = new LeafChange("d","开发人员",2000);
LeafChange e = new LeafChange("e","开发人员",2000);
LeafChange f = new LeafChange("f","开发人员",3000);
LeafChange g = new LeafChange("g","销售人员",4000);
LeafChange i = new LeafChange("i","销售人员",5000);
LeafChange h = new LeafChange("h","CEO小秘",8000);
LeafChange j = new LeafChange("j","财务人员",3000);
LeafChange zhanggLaoLiu = new LeafChange("郑老六","研发部副总",20000);
//该生产出来的人都产生出来了,然后我们怎么组装这棵树
//首先是定义总经理下有三个部门经理
ceo.addSubordinate(developDep);
ceo.addSubordinate(salesDep);
ceo.addSubordinate(financeDep);
//总经理下还有一个秘书
ceo.addSubordinate(h);
//定义研发部门下的结构
developDep.addSubordinate(first);
developDep.addSubordinate(two);
developDep.addSubordinate(zhanggLaoLiu);
//看看开发两个开发小组有什么
first.addSubordinate(a);
first.addSubordinate(b);
first.addSubordinate(c);
two.addSubordinate(d);
two.addSubordinate(e);
two.addSubordinate(f);
//再看看销售部下的人员情况
salesDep.addSubordinate(g);
salesDep.addSubordinate(i);
//最后一个财务
financeDep.addSubordinate(j);
return ceo;
}
public static String getTreeInfo(BranchChange root){
// ArrayList<ICorp> subordinateList = root.getSubordinate();
ArrayList<Corp> subordinateList = root.getSubordinate();
String info = "";
for (Corp s:subordinateList
) {
if (s instanceof LeafChange){//是员工就直接获得信息
info = info+s.getInfo()+"\n";
}else{//是个小头目
info = info + s.getInfo()+"\n"+getTreeInfo((BranchChange) s);
}
}
return info;
}
一个非常清楚的树状人员资源管理图出现了,那我们的程序是否还可以优化?可以!你看Leaf和Branch中都有getInfo信息,是否可以抽象,好,我们抽象一下:
接口没有了,改成抽象类了,IBranch接口也没有了,直接把方法放到实现类中,那我们先看抽象类:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 定义一个公司的人员的抽象类
*/
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;
}
}
抽象类嘛,就应该抽象出一些共性的东西出来,然后看两个具体的实现类:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
*/
public class LeafChange extends Corp {
//就写一个构造函数,这个是必须的
public LeafChange(String _name,String _position,int _salary){
super(_name,_position,_salary);
}
}
下面是小头目的实现类:
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 这些树枝节点 也就是这些领导们既要有自己的信息,还要知道自己下属的情况
*/
public class BranchChange extends Corp {
public BranchChange(String name, String position, int salary) {
super(name, position, salary);
}
//领导下面有哪些下级领导和小兵
ArrayList<Corp> subordinateList = new ArrayList<Corp>();
public void addSubordinate(Corp corp) {
this.subordinateList.add(corp);
}
public ArrayList<Corp> getSubordinate() {
return this.subordinateList;
}
}
也缩减了很多,再看Client.java程序,这个就没有多大变化了:
//把整个树组装出来
public static BranchChange compositionCropTree(){
//首先产生一个根节点
BranchChange ceo = new BranchChange("王大麻子","总经理",100000);
//产生三个部门经理,也就是树枝节点
BranchChange developDep = new BranchChange("刘大瘸子","研发部经理",10000);
BranchChange salesDep = new BranchChange("马二拐子","销售部经理",20000);
BranchChange financeDep = new BranchChange("赵三驼子","财务部经理",30000);
//再把三个小组长产生出来
BranchChange first = new BranchChange("杨三","开发一组组长",5000);
BranchChange two = new BranchChange("张大棒槌","开发二组组长",6000);
//剩下的就是我们这些小兵了,就死路人甲,路人乙
LeafChange a = new LeafChange("a","开发人员",2000);
LeafChange b = new LeafChange("b","开发人员",2000);
LeafChange c = new LeafChange("c","开发人员",2000);
LeafChange d = new LeafChange("d","开发人员",2000);
LeafChange e = new LeafChange("e","开发人员",2000);
LeafChange f = new LeafChange("f","开发人员",3000);
LeafChange g = new LeafChange("g","销售人员",4000);
LeafChange i = new LeafChange("i","销售人员",5000);
LeafChange h = new LeafChange("h","CEO小秘",8000);
LeafChange j = new LeafChange("j","财务人员",3000);
LeafChange zhanggLaoLiu = new LeafChange("郑老六","研发部副总",20000);
//该生产出来的人都产生出来了,然后我们怎么组装这棵树
//首先是定义总经理下有三个部门经理
ceo.addSubordinate(developDep);
ceo.addSubordinate(salesDep);
ceo.addSubordinate(financeDep);
//总经理下还有一个秘书
ceo.addSubordinate(h);
//定义研发部门下的结构
developDep.addSubordinate(first);
developDep.addSubordinate(two);
developDep.addSubordinate(zhanggLaoLiu);
//看看开发两个开发小组有什么
first.addSubordinate(a);
first.addSubordinate(b);
first.addSubordinate(c);
two.addSubordinate(d);
two.addSubordinate(e);
two.addSubordinate(f);
//再看看销售部下的人员情况
salesDep.addSubordinate(g);
salesDep.addSubordinate(i);
//最后一个财务
financeDep.addSubordinate(j);
return ceo;
}
public static String getTreeInfo(BranchChange root){
// ArrayList<ICorp> subordinateList = root.getSubordinate();
ArrayList<Corp> subordinateList = root.getSubordinate();
String info = "";
for (Corp s:subordinateList
) {
if (s instanceof LeafChange){//是员工就直接获得信息
info = info+s.getInfo()+"\n";
}else{//是个小头目
info = info + s.getInfo()+"\n"+getTreeInfo((BranchChange) s);
}
}
return info;
}
确实是类、接口减少了很多,而且程序也简单很多,但是大家可能还是很迷茫,这个Client程序并没有改变多少啊,非常正确,树的组装你是跑不了的,你要知道在项目中使用数据库来存储这些信息的,你从数据库中提取出来出来哪些人要分配到树枝,哪些人要分配到树叶,树枝与树枝、树叶的关系,这些都需要人去定义,通常这里使用一个界面去配置,在数据库中是一个标志信息,例如定义这样一个表:
从这张表中已经定义了一个树状结构,我们要做的就是从数据库中读取出来,然后展现在前台上,这个读取就用个for循环加上递归是不是就可以把一颗树建立起来?我们程序中其实还包含了数据的读取和加工,用了数据库后,数据和逻辑已经在表中定义好了,我们直接读取放在树上就可以了,这个还是比较容易做的了,大家不妨自己考虑一下。
上面我们讲到的就是组合模式(也叫做合成模式),主要用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:
我们先来说说组合模式的几个角色:
抽象构建角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为和属性;比如我们例子中的getInfo就封装到了抽象类中。
叶子构件(Leaf):叶子对象,其下再也没有其他的分支
树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点
组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:
从这个类图上大家应该能看清楚了,这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add(),remove()以及getChildren等方法,不管叶子对象还是树枝对象都有相同的结构,通过判断是getChildren的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题的,不是很贱的方式;安全模式就不同了,它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。
组合模式的有点有哪些呢?第一个有点只要是树形结构,就要考虑使用组合模式,这个一定要记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式。组合模式有一个非常明显的缺点,我们在Client.java中的定义了树叶和树枝使用时的定义了吗?如下:
BranchChange ceo = new BranchChange("王大麻子","总经理",100000);
//产生三个部门经理,也就是树枝节点
BranchChange developDep = new BranchChange("刘大瘸子","研发部经理",10000);
BranchChange salesDep = new BranchChange("马二拐子","销售部经理",20000);
BranchChange financeDep = new BranchChange("赵三驼子","财务部经理",30000);
发现了问题了吗?直接使用了实现类!这个在面向接口编程上是很不恰当的,这个在使用的时候要考虑清楚。
还有一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这棵树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理呢?
看类图:
看图中的红色方框,只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁,我们来看一下程序的改变:
package com.nextvpu.myapplication;
/**
* Created by NEXTVPU on 2018/6/10.
* 定义一个公司的人员的抽象类
*/
public abstract class Corp {
//根节点的名称
private String name = "";
//根节点的职位
private String position = "";
//根节点薪水
private int salary = 0;
//父节点是谁
private Corp parent = null;
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;
}
//设置父节点
protected void setParent(Corp _parent){
this.parent = _parent;
}
//得到父节点
public Corp getParent(){
return this.parent;
}
}
再来看看Branch.java的改变:
package com.nextvpu.myapplication;
import java.util.ArrayList;
/**
* Created by NEXTVPU on 2018/6/10.
* 这些树枝节点 也就是这些领导们既要有自己的信息,还要知道自己下属的情况
*/
public class BranchChange extends Corp {
public BranchChange(String name, String position, int salary) {
super(name, position, salary);
}
//领导下面有哪些下级领导和小兵
ArrayList<Corp> subordinateList = new ArrayList<Corp>();
public void addSubordinate(Corp corp) {
corp.setParent(this);
this.subordinateList.add(corp);
}
public ArrayList<Corp> getSubordinate() {
return this.subordinateList;
}
}
就是在每个节点甭管是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后你看整棵树就除了根节点外每个节点都一个父节点。