组合模式

大家在上学的时候应该都学过“数据结构”这门课程吧,还记得其中有一节叫“二叉树”吧,我们上学那会儿这一章节是必考内容,左子树,右子树,什么先序遍历后序遍历什么,重点就是二叉树的的遍历,我还记得当时老师就说,考试的时候一定有二叉树的构建和遍历,现在想起来还是觉的老师是正确的,树状结果在实际项目应用的非常广泛。
咱就先说个常见的例子,公司的人事管理就是一个典型的树状结构,你想想你公司的结构是不是这样:



从高的老大,往下一层一层的管理,后到我们这层小兵,很典型的树状结构(说明一下,这不是二叉树,有关二叉树的定义可以翻翻以前的教科书),我们今天的任务就是要把这个树状结构实现出来,并且还要把它遍历一遍,你要确认你建立的树是否有问题呀。
从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工 A、员工 D 等),我们增加一点学术术语上去,总经理叫做根节点(是不是想到 XML 中的那个根节点 root,那就对了),类似研发部经理有分支的节点叫做树枝节点,类似员工 A 的无分支的节点叫做树叶节点,都很形象,三个类型的的节点,那是不是定义三个类就可以?好,我们按照这个思路走下去,先看我们自己设计的类图:



这个类图是初学者容易想到的类图(如果你已经看明白这个类图的缺陷了,就可以不看下边的实现了,我是循序渐进的讲课,呵呵),我那来看这个实现:
先看高级别的根节点的实现:
package com.qssqc.composite;

import java.util.ArrayList;

/**
* 定义一个根节点,就为总经理服务
* 
* @author 清水三千尺
*
*/
public interface IRoot {
  // 得到总经理的信息
  public String getInfo();

  // 总经理下边要有小兵,那要能增加小兵,比如研发部总经理,这是个树枝节点
  public void add(IBranch branch);

  // 那要能增加树叶节点
  public void add(ILeaf leaf);

  // 既然能增加,那要还要能够遍历,不可能总经理不知道他手下有哪些人
  public ArrayList getSubordinateInfo();
}

这个根节点就是我们的总经理 CEO,然后看实现类:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 根节点的实现类
* 
* @author 清水三千尺
*
*/
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;
  }

  // 增加树枝节点
  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;
  }

}

很简单,通过构造函数传入参数,然后获得信息,还可以增加子树枝节点(部门经理)和叶子节点(秘书)。我们再来看 IBranch.java:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 树枝节点,也就是各个部门经理和组长的角色
* 
* @author 清水三千尺
*
*/
public interface IBranch {
  // 获得信息
  public String getInfo();

  // 增加数据节点,例如研发部下的研发一组
  public void add(IBranch branch);

  // 增加叶子节点
  public void add(ILeaf leaf);

  // 获得下级信息
  public ArrayList getSubordinateInfo();
}

下面是树枝节点的实现类:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 所有的树枝节点
* 
* @author 清水三千尺
*
*/
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;
  }
}

后看叶子节点,也就是员工的接口:

package com.qssqc.composite;

/**
* 叶子节点,也就是小的小兵了,只能自己干活,不能指派别人了
* 
* @author 清水三千尺
*
*/
public interface ILeaf {
  // 获得自己的信息呀
  public String getInfo();
}

下面是叶子节点的实现类:

package com.qssqc.composite;

/**
* 小的叶子节点
* 
* @author 清水三千尺
*
*/
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.java 程序:

package com.qssqc.composite;
/**
* Client的作用是组装这棵树,并遍历一遍 
* 
* @author 清水三千尺
*
*/

import java.util.ArrayList;

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++) {
          // 定义一个ArrayList长度,不要在for循环中每次计算
          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());
          }
      }
  }

}

这个程序比较长,如果是在我们的项目中有这样的程序,肯定是被拉出来做典型的,你写一大坨的程序给谁呀,以后还要维护的,程序是要短小精悍!幸运的是,我们是这为案例来讲解,而且就是指出这样组装这棵树是有问题,等会我们深入讲解,先看运行结果:



和我们期望要的结果一样,一棵完整的树就生成了,而且我们还能够遍历。看类图或程序的时候,你有没有发觉有问题?getInfo 每个接口都有为什么不能抽象出来?Root 类和 Branch 类有什么差别?为什么要定义成两个接口两个类?如果我要加一个任职期限,你是不是每个类都需要修改?如果我要后序遍历(从员工找到他的上级领导)能做吗?——彻底晕菜了!
问题很多,我们一个一个解决,先说抽象的问题,确实可以吧 IBranch 和 IRoot 合并成一个接口,这个我们先肯定下来,这是个比较大的改动,我们先画个类图:



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

类图上有两个接口,ICorp 是公司所有人员的信息的接口类,不管你是经理还是员工,你都有名字,职位,薪水,这个定义成一个接口没有错,IBranch 有没有必要呢?我们先实现出来然后再说。
先看 ICorp.java 源代码:

package com.qssqc.composite.advance;

/**
* 公司类,定义每个员工都有信息
* 
* @author 清水三千尺
*
*/
public interface ICorp {
  // 每个员工都有信息,你想隐藏,门儿都没有!
  public String getInfo();
}

接口很简单,只有一个方法,就是获得员工的信息,我们再来看实现类:

package com.qssqc.composite.advance;

/**
* Leaf是树叶节点,在这里就是我们这些小兵
* 
* @author 清水三千尺
*
*/
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;
  }

  // 获得小兵的信息
  public String getInfo() {
      String info = "";
      info = "姓名:" + this.name;
      info = info + "\t职位:" + this.position;
      info = info + "\t薪水:" + this.salary;
      return info;
  }
}

小兵就只有这些信息了,我们是具体干活的,我们是管理不了其他同事的,我们来看看那些经理和小组长是怎么实现的,先看接口:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 这些下边有小兵或者是经理等风云人物
* 
* @author 清水三千尺
*
*/
public interface IBranch {
  // 能够增加小兵(树叶节点)或者是经理(树枝节点)
  public void addSubordinate(ICorp corp);

  // 我还要能够获得下属的信息
  public ArrayList<ICorp> getSubordinate();
  
  /*
   * 本来还应该有一个方法delSubordinate(ICorp corp),删除下属 
   * 这个方法我们没有用到就不写进来了
   */
}

接口也是很简单的,下面是实现类:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 这些树枝节点也就是这些领导们既要有自己的信息,还要知道自己的下属情况
* 
* @author 清水三千尺
*
*/
public class Branch implements IBranch, ICorp {
  // 领导也是人,也有名字
  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.java 程序:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 组装这个树形结构,并展示出来
* 
* @author 清水三千尺
*
*/
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 Branch compositeCorpTree() {
      // 首先产生总经理CEO
      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);
      // 开始组装
      // CEO下有三个部门经理和一个秘书
      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 信息,是否可以抽象,好,我们抽象一下:



你一看这个图,乐了,能不乐嘛,减少很多工作量了,接口没有了,改成抽象类了,IBranch 接口也没有了,直接把方法放到了实现类中了,那我们先来看抽象类:
package com.qssqc.composite.perfect;

/**
* 定义一个公司的人员的抽象类
* 
* @author 清水三千尺
*
*/
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.qssqc.composite.perfect;

/**
* 普通员工很简单,就写一个构造函数就可以了
* 
* @author 清水三千尺
*
*/
public class Leaf extends Corp {
  // 就写一个构造函数,这个是必须的
  public Leaf(String _name, String _position, int _salary) {
      super(_name, _position, _salary);
  }
}

这个改动比较多,就几行代码就完成了,确实就应该这样,下面是小头目的实现类:

package com.qssqc.composite.perfect;

import java.util.ArrayList;

/**
* 节点类,也简单了很多
* 
* @author 清水三千尺
*
*/
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;
  }
}

也缩减了很多,再看 Client.java 程序,这个就没有多大变化了:

package com.qssqc.composite.perfect;

import java.util.ArrayList;

/**
* 组装这个树形结构,并展示出来
* 
* @author 清水三千尺
*
*/
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 Branch compositeCorpTree() {
      // 首先产生总经理CEO
      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);
      // 开始组装
      // CEO下有三个部门经理和一个秘书
      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<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;
  }

}

就是把用到 ICorp 接口的地方修改为 Corp 抽象类就成了,就上面黄色的部分作了点修改,其他保持不变,运行结果还是保持一样:



确实是类、接口减少了很多,而且程序也简单很多,但是大家可能还是很迷茫,这个 Client 程序并没有改变多少呀,非常正确,树的组装你是跑不了的,你要知道在项目中使用数据库来存储这些信息的,你从数据库中提出出来哪些人要分配到树枝,哪些人要分配到树叶,树枝与树枝、树叶的关系,这些都需要人去定义,通常这里使用一个界面去配置,在数据库中是一个标志信息,例如定义这样一张表:

主键 唯一编码 名称 是否是叶子节点 父节点
1 CEO 总经理
2 developDep 研发部经理 CEO
3 salesDep 销售部经理 CEO
4 financeDep 财务部经理 CEO
5 k 总经理秘书 CEO
6 a 员工 A developed
7 b 员工 B Developed

从这张表中已经定义个一个树形结构,我们要做的就是从数据库中读取出来,然后展现到前台上,这个读取就用个 for 循环加上递归是不是就可以把一棵树建立起来?我们程序中其实还包涵了数据的读取和加工,用了数据库后,数据和逻辑已经在表中定义好了,我们直接读取放到树上就可以了,这个还是比较容易做了的,大家不妨自己考虑一下。
上面我们讲到的就是组合模式(也叫合成模式),有时又叫做部分-整体模式(Part-Whole),主要是用来描述整体与部分的关系,用的多的地方就是树形结构。组合模式通用类图如下:



我们先来说说组合模式的几个角色:
抽象构件角色(Component):定义参加组合的对象的共有方法和属性,可以定义一些默认的行为或属性;比如我们例子中的 getInfo 就封装到了抽象类中。
叶子构件(Leaf):叶子对象,其下再也没有其他的分支。
树枝构件(Composite):树枝对象,它的作用是组合树枝节点和叶子节点;
组合模式有两种模式,透明模式和安全模式,这两个模式有什么区别呢?先看类图:


透明模式类图

安全模式类图

从类图上大家应该能看清楚了,这两种模式各有优缺点,透明模式是把用来组合使用的方法放到抽象类中,比如add(),remove()以及getChildren等方法(顺便说一下,getChildren一般返回的结果为Iterable的实现类,很多,大家可以看 JDK 的帮助),不管叶子对象还是树枝对象都有相同的结构,通过判断是getChildren 的返回值确认是叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题的,不是很建议的方式;安全模式就不同了,它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方法比较安全,我们的例子使用了安全模式。
组合模式的优点有哪些呢?第一个优点只要是树形结构,就要考虑使用组合模式,这个一定记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。组合模式有一个非常明显的缺点,看到我们在 Client.java 中的的定义了树叶和树枝使用时的定义了吗?如下:

发现什么问题了吗?直接使用了实现类!这个在面向接口编程上是很不恰当的,这个在使用的时候要考虑清楚。
组合模式在项目中到处都有,比如现在的页面结构一般都是上下结构,上面放系统的 Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形的结构,比较清晰,这个 JavaScript有很多例子,大家可以到网上搜索一把;还有,我们的自己也是一个树状结构,根据我,能够找到我的父母,根据父亲又能找到爷爷奶奶,根据母亲能够找到外公外婆等等,很典型的树形结构,而且还很规范(这个要是不规范那肯定是乱套了)。
我们在上面也还提到了一个问题,就是树的遍历问题,从上到下遍历没有问题,但是我要是从下往上遍历呢?比如在人力资源这颗树上,我从中抽取一个用户,要找到它的上级有哪些,下级有哪些,怎么处理?想想,~~~,再想想!想出来了吧,我们对下答案,先看类图:



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

/**
* 定义一个公司的人员的抽象类
* 
* @author 清水三千尺
*
*/
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.qssqc.composite.extend;

import java.util.ArrayList;

/**
* 节点类,也简单了很多
* 
* @author 清水三千尺
*
*/
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) {
      corp.setParent(this); // 设置父节点
      this.subordinateList.add(corp);
  }

  // 我有哪些下属
  public ArrayList<Corp> getSubordinate() {
      return this.subordinateList;
  }
}

增加了黄色部分,看懂程序了吗?就是在每个节点甭管是树枝节点还是树叶节点,都增加了一个属性:父节点对象,这样在树枝节点增加子节点或叶子的时候设置父节点,然后你看整棵树就除了根节点外每个节点都一个父节点,剩下的事情还不好处理吗?每个节点上都有父节点了,你要往上找,那就找呗!Client程 序 我 就 不 写 了 , 今 天 已 经 拷 贝 的 代 码 实 在 有 点 多 , 大 家 自 己 考 虑 一 下 , 写 个 find 方 法 , 然 后 一 个 一 个 往上找,简单的方法了!
有 了 这 个 parent 属 性 , 什 么 后 序 遍 历 ( 从 下 往 上 找 )、中序遍历(从中间某个环节往上或往下遍历)都解决了,这个就不多说了。

结束语
代码结构

再提一个问题,树叶节点和树枝节点是有顺序的,你不能乱排的,怎么办?比如我们上面的例子,研发一组下边有三个成员,这三个成员是要进行排序的呀,你怎么处理?问我呀,问你呢,好好想想,以后用到着的!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容

  • 概述 UML类图 代码栗子 总结 概述概念 组合模式是指将对象组合成树形结构以表示“部分-整体”的层次结构,组合模...
    tanoak阅读 376评论 0 0
  • 结构型设计模式 12.组合模式 组合模式又称“整体-部分”设计模式,让整体与部分的使用具有一致性。 12.1创建抽...
    crazyydevil阅读 134评论 0 0
  • 组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式是的用户对单个对象和组合对象的使用具有一致性。...
    Mitchell阅读 377评论 0 0
  • 转:http://www.runoob.com/design-pattern/composite-pattern....
    right_33cb阅读 212评论 0 0
  • 类图: 精髓 树形结构,可以表现出对象的层次结构,与部分-整体的关系 (精髓中的精髓)节点实现同一个公共接口,使得...
    炫迈哥阅读 286评论 0 0