公司的人事管理是一个典型的树状结构:
![](https://img.haomeiwen.com/i7789414/705109614ced624c.png)
我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍.
从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工D等),总经理叫做根节点,类似研发部经理有分支的节点叫做树枝节点,类似员工A的无分支的节点叫做树叶节点,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:
![](https://img.haomeiwen.com/i7789414/ab722a8af51adff1.png)
以下是上述类图的实现:
/**
* 定义一个根节点,就为总经理服务
*/
public interface IRoot {
// 得到总经理的信息
String getInfo();
// 总经理下边要有小兵,那要能增加小兵,比如研发部经理,这是个树枝节点
void add(IBranch branch);
// 增加树叶节点
void add(ILeaf leaf);
// 遍历下属
ArrayList<Object> getSubordinateInfo();
}
/**
* 树枝节点,也就是各个部门经理和组长的角色
*/
public interface IBranch {
// 获取信息
String getInfo();
// 增加数据节点,例如研发部下的研发一组
void add(IBranch branch);
// 增加树叶节点
void add(ILeaf leaf);
// 获取下级信息
ArrayList<Object> getSubordinateInfo();
}
/**
* 叶子节点,也就是最小的小兵了,只能自己干活,不能指派别人了
*/
public interface ILeaf {
// 获得自己的信息
String getInfo();
}
/**
* 根节点的实现类
*/
public class Root implements IRoot {
// 保存根节点下的树枝节点和树叶节点,subordinate是下级的意思
private ArrayList<Object> 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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
// 增加树枝节点
@Override
public void add(IBranch branch) {
this.subordinateList.add(branch);
}
// 增加叶子节点,比如秘书,直接隶属于总经理
@Override
public void add(ILeaf leaf) {
this.subordinateList.add(leaf);
}
// 获得下级的信息
@Override
public ArrayList<Object> getSubordinateInfo() {
return this.subordinateList;
}
}
/**
* 树枝节点,就是各个部门经理和组长的角色
*/
public class Branch implements IBranch {
// 存储子节点的信息
private ArrayList<Object> 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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
// 增加一个子树枝节点
@Override
public void add(IBranch branch) {
this.subordinateList.add(branch);
}
// 增加一个叶子节点
@Override
public void add(ILeaf leaf) {
this.subordinateList.add(leaf);
}
// 获得下级的信息
@Override
public ArrayList<Object> getSubordinateInfo() {
return this.subordinateList;
}
}
/**
* 最小的叶子节点
*/
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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
}
好了,所有的根节点,树枝节点和叶子节点都已经实现了,从总经理、部门经理到最终的员工都已经实现了,然后的工作就是组装成一个树状结构和遍历这个树状结构,看Client类:
/**
* Client的作用是组装这棵树,并遍历一遍
*/
public class Client {
public static void main(String[] args) {
// 首先产生了一个根节点
IRoot ceo = new Root("王大麻子", "CEO", 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 zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
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);
// 组装这棵树
// 首先是定义总经理下有三个部门经理
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<Object> subordinateList) {
for (Object obj : subordinateList) {
if (obj instanceof Leaf) {
ILeaf leaf = (ILeaf)obj;
System.out.println(leaf.getInfo());
} else {
IBranch branch = (IBranch)obj;
System.out.println(branch.getInfo());
getAllSubordinateInfo(branch.getSubordinateInfo());
}
}
}
}
和我们期望要的结果一样,一棵完整的树就生成了,而且我们还能够遍历,但这样的类设计是有问题的,getInfo()
每个接口都有为什么不能抽象出来?Root
类和Branch
类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?
问题很多,我们一个一个解决,先说抽象的问题,确实可以把IBranch
和IRoot
合并成一个接口,这个我们先肯定下来,这是个比较大的改动,我们先画个类图:
![](https://img.haomeiwen.com/i7789414/86eb591e8d972f79.png)
这个类图还是有点问题的,接口的作用是什么?定义共性,那ILeaf
和IBranch
是不是也有共性呢?有getInfo()
,我们是不是把这个共性也已经封装起来,再修改一下类图:
![](https://img.haomeiwen.com/i7789414/799a2b812c521fbe.png)
类图上有两个接口,ICorp
是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字,职位,薪水,这个定义成一个接口没有错,IBranch
有没有必要呢?我们先实现出来然后再说:
/**
* 公司类,定义每个员工都有信息
*/
public interface ICorp {
// 获取信息
String getInfo();
}
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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
}
/**
* 树枝节点,有下属节点
*/
public interface IBranch {
// 能够增加小兵(树叶节点)或者是经理(树枝节点)
void addSubordinate(ICorp corp);
// 获取下级信息
ArrayList<ICorp> getSubordinateInfo();
}
/**
* 树枝节点,就是各个部门经理和组长的角色
*/
public class Branch implements IBranch, ICorp {
// 下级
private ArrayList<ICorp> 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 void addSubordinate(ICorp corp) {
this.subordinateList.add(corp);
}
@Override
public ArrayList<ICorp> getSubordinateInfo() {
return this.subordinateList;
}
// 获取自己的信息
@Override
public String getInfo() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
}
/**
* @author YangYunhe
* @date 2020-12-28 10:20
* @description 组装树形结构
*/
public class Client {
public static void main(String[] args) {
// 组装一个组织结构
Branch ceo = compositeCorpTree();
// 打印CEO的信息
System.out.println(ceo.getInfo());
// 打印所有员工的信息
System.out.println(getTreeInfo(ceo));
}
// 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
public static String getTreeInfo(Branch root) {
StringBuilder info = new StringBuilder();
ArrayList<ICorp> subordinateList = root.getSubordinateInfo();
for (ICorp iCorp : subordinateList) {
if(iCorp instanceof Leaf) {
info.append(iCorp.getInfo()).append("\n");
} else {
info.append(iCorp.getInfo()).append("\n").append(getTreeInfo((Branch)iCorp));
}
}
return info.toString();
}
public static Branch compositeCorpTree() {
// 首先产生了CEO
Branch ceo = new Branch("王大麻子", "CEO", 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 zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
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);
// 组装这棵树
// 定义CEO下的三个部门经理和一个秘书
ceo.addSubordinate(developDep);
ceo.addSubordinate(salesDep);
ceo.addSubordinate(financeDep);
// 总经理下还有一个秘书
ceo.addSubordinate(k);
// 定义研发部门下的结构
developDep.addSubordinate(firstDevGroup);
developDep.addSubordinate(secondDevGroup);
developDep.addSubordinate(zhengLaoLiu);
// 定义两个开发小组下的结构
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 ceo;
}
}
我们的程序还可以继续优化,Leaf
和Branch
中都有getInfo()
方法,可以抽象出来:
![](https://img.haomeiwen.com/i7789414/f3c282098e6e96b1.png)
/**
* 公司人员抽象类
*/
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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
}
/**
* 普通员工很简单,就写一个构造函数就可以了
*/
public class Leaf extends Corp {
public Leaf(String name, String position, int salary) {
super(name, position, salary);
}
}
/**
* 节点类,也简单了很多
*/
public class Branch extends Corp {
// 领导下边有那些下级领导和小兵
private ArrayList<Corp> subordinateList = new ArrayList<>();
public Branch(String name, String position, int salary) {
super(name, position, salary);
}
// 增加一个下属,可能是小头目,也可能是个小兵
public void addSubordinate(Corp corp) {
this.subordinateList.add(corp);
}
// 我有哪些下属
public ArrayList<Corp> getSubordinateInfo() {
return this.subordinateList;
}
}
public class Client {
public static void main(String[] args) {
// 组装一个组织结构
Branch ceo = compositeCorpTree();
// 打印CEO的信息
System.out.println(ceo.getInfo());
// 打印所有员工的信息
System.out.println(getTreeInfo(ceo));
}
// 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
public static String getTreeInfo(Branch root) {
StringBuilder info = new StringBuilder();
ArrayList<Corp> subordinateList = root.getSubordinateInfo();
for (Corp corp : subordinateList) {
if(corp instanceof Leaf) {
info.append(corp.getInfo()).append("\n");
} else {
info.append(corp.getInfo()).append("\n").append(getTreeInfo((Branch)corp));
}
}
return info.toString();
}
public static Branch compositeCorpTree() {
// 首先产生了CEO
Branch ceo = new Branch("王大麻子", "CEO", 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 zhengLaoLiu = new Leaf("郑老六", "研发部副总", 20000);
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);
// 组装这棵树
// 定义CEO下的三个部门经理和一个秘书
ceo.addSubordinate(developDep);
ceo.addSubordinate(salesDep);
ceo.addSubordinate(financeDep);
// 总经理下还有一个秘书
ceo.addSubordinate(k);
// 定义研发部门下的结构
developDep.addSubordinate(firstDevGroup);
developDep.addSubordinate(secondDevGroup);
developDep.addSubordinate(zhengLaoLiu);
// 定义两个开发小组下的结构
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 ceo;
}
}
经过这样一步步的改造,类、接口减少了很多,而且程序也简单了很多。
上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:
![](https://img.haomeiwen.com/i7789414/f8ca3d4e779caca8.png)
我们先来说说组合模式的几个角色:
-
抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的
getInfo()
就封装到了抽象类中。 -
叶子构件(Leaf):叶子对象,其下再也没有其他的分支。
-
树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;
组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:
![](https://img.haomeiwen.com/i7789414/4a9082dcc001ef4e.png)
![](https://img.haomeiwen.com/i7789414/8b50f0f8017a3b3c.png)
这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add()
、remove()
以及getChildren()
等方法,(顺便说一下,getChildren()
一般返回的结果为Iterable
的实现类),不管叶子对象还是树枝对象都有相同的结构,通过判断getChildren
的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题,不建议使用这种方式;安全模式把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。
组合模式的优缺点:
-
只要是树形结构,就要考虑使用组合模式,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,就可以考虑使用组合模式吧
-
组合模式有一个非常明显的缺点,在
Client
类中的的定义树叶和树枝使用时使用了如下代码:Branch developDep = new Branch("刘大瘸子","研发部门经理",10000); Leaf g = new Leaf("g","开发人员",2000);
直接使用了实现类去创建对象,这个在面向接口编程上是很不恰当的。
我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?先看类图:
![](https://img.haomeiwen.com/i7789414/1b4e237c33b9845e.png)
看类图中的红色方框,只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁,我们来看一下程序的改变:
/**
* 公司人员抽象类
*/
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() {
return String.format("名称: %s\t职位: %s\t薪水: %s", this.name, this.position, this.salary);
}
/**
* 增加了以下两个方法
*/
// 设置父节点
protected void setParent(Corp parent) {
this.parent = parent;
}
// 得到父节点
public Corp getParent() {
return this.parent;
}
}
/**
* 节点类
*/
public class Branch extends Corp {
// 领导下边有那些下级领导和小兵
private ArrayList<Corp> subordinateList = new ArrayList<>();
public Branch(String name, String position, int salary) {
super(name, position, salary);
}
// 增加一个下属,可能是小头目,也可能是个小兵
public void addSubordinate(Corp corp) {
// 重要的是这行,添加下属的时候给下属设置父节点为自己
corp.setParent(this);
this.subordinateList.add(corp);
}
// 我有哪些下属
public ArrayList<Corp> getSubordinateInfo() {
return this.subordinateList;
}
}
每个节点无论是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后整棵树除了根节点外每个
节点都一个父节点,这样每个节点上都有父节点了,有了这个parent
属性,后序遍历(从下往上找)、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。再提一个扩展问题,树叶节点和树枝节点是有顺序的,比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的,这种情况怎么处理?
本文原书:
《您的设计模式》 作者:CBF4LIFE
网友评论