设计模式-组合模式

上大学的时候大家都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,考试的时候一定有二叉树的构建和遍历,树状结果在实际项目应用的非常广泛。
咱先说这个最常见的例子,公司的人事管理就是一个典型的树状结构,你想想你公司的结构是不是这样:
这里写图片描述
从最高的老大,往下一层一层的管理,最后到我们这层小兵,很典型的树状结构(这不是二叉树),我们今天的任务就是把这个树状结构实现出来,并且把它遍历一边,你要确认你建立的树是否有问题啊。
从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工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;
    }
}

就是在每个节点甭管是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后你看整棵树就除了根节点外每个节点都一个父节点。

组合模式Demo下载地址:

组合模式demo下载

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值