美文网首页
015.组合模式

015.组合模式

作者: CoderJed | 来源:发表于2020-12-29 16:51 被阅读0次

    公司的人事管理是一个典型的树状结构:

    我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍.

    从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工A、员工D等),总经理叫做根节点,类似研发部经理有分支的节点叫做树枝节点,类似员工A的无分支的节点叫做树叶节点,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:

    以下是上述类图的实现:

    /**
     * 定义一个根节点,就为总经理服务
     */
    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类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?

    问题很多,我们一个一个解决,先说抽象的问题,确实可以把IBranchIRoot合并成一个接口,这个我们先肯定下来,这是个比较大的改动,我们先画个类图:

    这个类图还是有点问题的,接口的作用是什么?定义共性,那ILeafIBranch是不是也有共性呢?有getInfo(),我们是不是把这个共性也已经封装起来,再修改一下类图:

    类图上有两个接口,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;
        }
    
    }
    

    我们的程序还可以继续优化,LeafBranch中都有getInfo() 方法,可以抽象出来:

    /**
     * 公司人员抽象类
     */
    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),主要是用来描述整体与部分的关系,用的最多的地方就是树形结构。组合模式通用类图如下:

    我们先来说说组合模式的几个角色:

    • 抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的getInfo() 就封装到了抽象类中。

    • 叶子构件(Leaf):叶子对象,其下再也没有其他的分支。

    • 树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;

    组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:

    这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add()remove()以及getChildren() 等方法,(顺便说一下,getChildren() 一般返回的结果为Iterable的实现类),不管叶子对象还是树枝对象都有相同的结构,通过判断getChildren 的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题,不建议使用这种方式;安全模式把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。

    组合模式的优缺点:

    • 只要是树形结构,就要考虑使用组合模式,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,就可以考虑使用组合模式吧

    • 组合模式有一个非常明显的缺点,在Client类中的的定义树叶和树枝使用时使用了如下代码:

      Branch developDep = new Branch("刘大瘸子","研发部门经理",10000);
      Leaf g = new Leaf("g","开发人员",2000);
      

      直接使用了实现类去创建对象,这个在面向接口编程上是很不恰当的。

    我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?先看类图:

    看类图中的红色方框,只要增加两个方法就可以了,一个是设置父节点是谁,一个是查找父节点是谁,我们来看一下程序的改变:

    /**
     * 公司人员抽象类
     */
    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

    相关文章

      网友评论

          本文标题:015.组合模式

          本文链接:https://www.haomeiwen.com/subject/nqubgktx.html