組合模式

大家在上學(xué)的時候應(yīng)該都學(xué)過“數(shù)據(jù)結(jié)構(gòu)”這門課程吧,還記得其中有一節(jié)叫“二叉樹”吧,我們上學(xué)那會兒這一章節(jié)是必考內(nèi)容,左子樹,右子樹,什么先序遍歷后序遍歷什么,重點就是二叉樹的的遍歷,我還記得當(dāng)時老師就說,考試的時候一定有二叉樹的構(gòu)建和遍歷,現(xiàn)在想起來還是覺的老師是正確的,樹狀結(jié)果在實際項目應(yīng)用的非常廣泛。
咱就先說個常見的例子,公司的人事管理就是一個典型的樹狀結(jié)構(gòu),你想想你公司的結(jié)構(gòu)是不是這樣:



從高的老大,往下一層一層的管理,后到我們這層小兵,很典型的樹狀結(jié)構(gòu)(說明一下,這不是二叉樹,有關(guān)二叉樹的定義可以翻翻以前的教科書),我們今天的任務(wù)就是要把這個樹狀結(jié)構(gòu)實現(xiàn)出來,并且還要把它遍歷一遍,你要確認(rèn)你建立的樹是否有問題呀。
從這個樹狀結(jié)構(gòu)上分析,有兩種節(jié)點:有分支的節(jié)點(如研發(fā)部經(jīng)理)和無分支的節(jié)點(如員工 A、員工 D 等),我們增加一點學(xué)術(shù)術(shù)語上去,總經(jīng)理叫做根節(jié)點(是不是想到 XML 中的那個根節(jié)點 root,那就對了),類似研發(fā)部經(jīng)理有分支的節(jié)點叫做樹枝節(jié)點,類似員工 A 的無分支的節(jié)點叫做樹葉節(jié)點,都很形象,三個類型的的節(jié)點,那是不是定義三個類就可以?好,我們按照這個思路走下去,先看我們自己設(shè)計的類圖:



這個類圖是初學(xué)者容易想到的類圖(如果你已經(jīng)看明白這個類圖的缺陷了,就可以不看下邊的實現(xiàn)了,我是循序漸進(jìn)的講課,呵呵),我那來看這個實現(xiàn):
先看高級別的根節(jié)點的實現(xiàn):
package com.qssqc.composite;

import java.util.ArrayList;

/**
* 定義一個根節(jié)點,就為總經(jīng)理服務(wù)
* 
* @author 清水三千尺
*
*/
public interface IRoot {
  // 得到總經(jīng)理的信息
  public String getInfo();

  // 總經(jīng)理下邊要有小兵,那要能增加小兵,比如研發(fā)部總經(jīng)理,這是個樹枝節(jié)點
  public void add(IBranch branch);

  // 那要能增加樹葉節(jié)點
  public void add(ILeaf leaf);

  // 既然能增加,那要還要能夠遍歷,不可能總經(jīng)理不知道他手下有哪些人
  public ArrayList getSubordinateInfo();
}

這個根節(jié)點就是我們的總經(jīng)理 CEO,然后看實現(xiàn)類:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 根節(jié)點的實現(xiàn)類
* 
* @author 清水三千尺
*
*/
public class Root implements IRoot {

  // 保存根節(jié)點下的樹枝節(jié)點和樹葉節(jié)點,Subordinate的意思是下級
  private ArrayList subordinateList = new ArrayList();
  // 根節(jié)點的名稱
  private String name = "";
  // 根節(jié)點的職位
  private String position = "";
  // 根節(jié)點的薪水
  private int salary = 0;

  // 通過構(gòu)造函數(shù)傳遞進(jìn)來總經(jīng)理的信息
  public Root(String name, String position, int salary) {
      this.name = name;
      this.position = position;
      this.salary = salary;
  }

  // 增加樹枝節(jié)點
  public void add(IBranch branch) {
      this.subordinateList.add(branch);
  }

  // 增加葉子節(jié)點,比如秘書,直接隸屬于總經(jīng)理
  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;
  }

}

很簡單,通過構(gòu)造函數(shù)傳入?yún)?shù),然后獲得信息,還可以增加子樹枝節(jié)點(部門經(jīng)理)和葉子節(jié)點(秘書)。我們再來看 IBranch.java:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 樹枝節(jié)點,也就是各個部門經(jīng)理和組長的角色
* 
* @author 清水三千尺
*
*/
public interface IBranch {
  // 獲得信息
  public String getInfo();

  // 增加數(shù)據(jù)節(jié)點,例如研發(fā)部下的研發(fā)一組
  public void add(IBranch branch);

  // 增加葉子節(jié)點
  public void add(ILeaf leaf);

  // 獲得下級信息
  public ArrayList getSubordinateInfo();
}

下面是樹枝節(jié)點的實現(xiàn)類:

package com.qssqc.composite;

import java.util.ArrayList;

/**
* 所有的樹枝節(jié)點
* 
* @author 清水三千尺
*
*/
public class Branch implements IBranch {
  // 存儲子節(jié)點的信息
  private ArrayList subordinateList = new ArrayList();
  // 樹枝節(jié)點的名稱
  private String name = "";
  // 樹枝節(jié)點的職位
  private String position = "";
  // 樹枝節(jié)點的薪水
  private int salary = 0;

  // 通過構(gòu)造函數(shù)傳遞樹枝節(jié)點的參數(shù)
  public Branch(String name, String position, int salary) {
      this.name = name;
      this.position = position;
      this.salary = salary;
  }

  // 增加一個子樹枝節(jié)點
  public void add(IBranch branch) {
      this.subordinateList.add(branch);
  }

  // 增加一個葉子節(jié)點
  public void add(ILeaf leaf) {
      this.subordinateList.add(leaf);
  }

  // 獲得自己樹枝節(jié)點的信息
  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;
  }
}

后看葉子節(jié)點,也就是員工的接口:

package com.qssqc.composite;

/**
* 葉子節(jié)點,也就是小的小兵了,只能自己干活,不能指派別人了
* 
* @author 清水三千尺
*
*/
public interface ILeaf {
  // 獲得自己的信息呀
  public String getInfo();
}

下面是葉子節(jié)點的實現(xiàn)類:

package com.qssqc.composite;

/**
* 小的葉子節(jié)點
* 
* @author 清水三千尺
*
*/
public class Leaf implements ILeaf {

  // 葉子叫什么名字
  private String name = "";
  // 葉子的職位
  private String position = "";
  // 葉子的薪水
  private int salary = 0;

  // 通過構(gòu)造函數(shù)傳遞信息
  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;
  }

}

好了,所有的根節(jié)點,樹枝節(jié)點和葉子節(jié)點都已經(jīng)實現(xiàn)了,從總經(jīng)理、部門經(jīng)理到終的員工都已經(jīng)實現(xiàn)了,然后的工作就是組裝成一個樹狀結(jié)構(gòu)和遍歷這個樹狀結(jié)構(gòu),看 Client.java 程序:

package com.qssqc.composite;
/**
* Client的作用是組裝這棵樹,并遍歷一遍 
* 
* @author 清水三千尺
*
*/

import java.util.ArrayList;

public class Client {
  public static void main(String[] args) {
      // 首先產(chǎn)生了一個根節(jié)點
      IRoot ceo = new Root("王大麻子", "總經(jīng)理", 100000);
      // 產(chǎn)生三個部門經(jīng)理,也就是樹枝節(jié)點
      IBranch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      IBranch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      IBranch financeDep = new Branch("趙三駝子", "財務(wù)部經(jīng)理", 30000);
      // 再把三個小組長產(chǎn)生出來
      IBranch firstDevGroup = new Branch("楊三乜斜", "開發(fā)一組組長", 5000);
      IBranch secondDevGroup = new Branch("吳大棒槌", "開發(fā)二組組長", 6000);
      // 剩下的及時我們這些小兵了,就是路人甲,路人乙
      ILeaf a = new Leaf("a", "開發(fā)人員", 2000);
      ILeaf b = new Leaf("b", "開發(fā)人員", 2000);
      ILeaf c = new Leaf("c", "開發(fā)人員", 2000);
      ILeaf d = new Leaf("d", "開發(fā)人員", 2000);
      ILeaf e = new Leaf("e", "開發(fā)人員", 2000);
      ILeaf f = new Leaf("f", "開發(fā)人員", 2000);
      ILeaf g = new Leaf("g", "開發(fā)人員", 2000);
      ILeaf h = new Leaf("h", "銷售人員", 5000);
      ILeaf i = new Leaf("i", "銷售人員", 4000);
      ILeaf j = new Leaf("j", "財務(wù)人員", 5000);
      ILeaf k = new Leaf("k", "CEO秘書", 8000);
      ILeaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副總", 20000);
      // 該產(chǎn)生的人都產(chǎn)生出來了,然后我們怎么組裝這棵樹
      // 首先是定義總經(jīng)理下有三個部門經(jīng)理
      ceo.add(developDep);
      ceo.add(salesDep);
      ceo.add(financeDep);
      // 總經(jīng)理下還有一個秘書
      ceo.add(k);
      // 定義研發(fā)部門 下的結(jié)構(gòu)
      developDep.add(firstDevGroup);
      developDep.add(secondDevGroup);
      // 研發(fā)部經(jīng)理下還有一個副總
      developDep.add(zhengLaoLiu);
      // 看看開發(fā)兩個開發(fā)小組下有什么
      firstDevGroup.add(a);
      firstDevGroup.add(b);
      firstDevGroup.add(c);
      secondDevGroup.add(d);
      secondDevGroup.add(e);
      secondDevGroup.add(f);
      // 再看銷售部下的人員情況
      salesDep.add(h);
      salesDep.add(i);
      // 后一個財務(wù)
      financeDep.add(j);
      // 樹狀結(jié)構(gòu)寫完畢,然后我們打印出來
      System.out.println(ceo.getInfo());
      // 打印出來整個樹形
      getAllSubordinateInfo(ceo.getSubordinateInfo());
  }

  // 遍歷所有的樹枝節(jié)點,打印出信息
  private static void getAllSubordinateInfo(ArrayList subordinateList) {
      int length = subordinateList.size();
      for (int m = 0; m < length; m++) {
          // 定義一個ArrayList長度,不要在for循環(huán)中每次計算
          Object s = subordinateList.get(m);
          if (s instanceof Leaf) {
              // 是個葉子節(jié)點,也就是員工
              ILeaf employee = (ILeaf) s;
              System.out.println(((Leaf) s).getInfo());
          } else {
              IBranch branch = (IBranch) s;
              System.out.println(branch.getInfo());
              // 再遞歸調(diào)用
              getAllSubordinateInfo(branch.getSubordinateInfo());
          }
      }
  }

}

這個程序比較長,如果是在我們的項目中有這樣的程序,肯定是被拉出來做典型的,你寫一大坨的程序給誰呀,以后還要維護(hù)的,程序是要短小精悍!幸運(yùn)的是,我們是這為案例來講解,而且就是指出這樣組裝這棵樹是有問題,等會我們深入講解,先看運(yùn)行結(jié)果:



和我們期望要的結(jié)果一樣,一棵完整的樹就生成了,而且我們還能夠遍歷。看類圖或程序的時候,你有沒有發(fā)覺有問題?getInfo 每個接口都有為什么不能抽象出來?Root 類和 Branch 類有什么差別?為什么要定義成兩個接口兩個類?如果我要加一個任職期限,你是不是每個類都需要修改?如果我要后序遍歷(從員工找到他的上級領(lǐng)導(dǎo))能做嗎?——徹底暈菜了!
問題很多,我們一個一個解決,先說抽象的問題,確實可以吧 IBranch 和 IRoot 合并成一個接口,這個我們先肯定下來,這是個比較大的改動,我們先畫個類圖:



這個類圖還是有點問題的,接口的作用是什么?定義共性,那 ILeaf 和 IBranch 是不是也有共性呢?有 getInfo(),我們是不是要把這個共性也已經(jīng)封裝起來呢?好,我們再修改一下類圖:

類圖上有兩個接口,ICorp 是公司所有人員的信息的接口類,不管你是經(jīng)理還是員工,你都有名字,職位,薪水,這個定義成一個接口沒有錯,IBranch 有沒有必要呢?我們先實現(xiàn)出來然后再說。
先看 ICorp.java 源代碼:

package com.qssqc.composite.advance;

/**
* 公司類,定義每個員工都有信息
* 
* @author 清水三千尺
*
*/
public interface ICorp {
  // 每個員工都有信息,你想隱藏,門兒都沒有!
  public String getInfo();
}

接口很簡單,只有一個方法,就是獲得員工的信息,我們再來看實現(xiàn)類:

package com.qssqc.composite.advance;

/**
* Leaf是樹葉節(jié)點,在這里就是我們這些小兵
* 
* @author 清水三千尺
*
*/
public class Leaf implements ICorp {
  // 小兵也有名稱
  private String name = "";
  // 小兵也有職位
  private String position = "";
  // 小兵也有薪水,否則誰給你干
  private int salary = 0;

  // 通過一個構(gòu)造函數(shù)傳遞小兵的信息
  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;
  }
}

小兵就只有這些信息了,我們是具體干活的,我們是管理不了其他同事的,我們來看看那些經(jīng)理和小組長是怎么實現(xiàn)的,先看接口:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 這些下邊有小兵或者是經(jīng)理等風(fēng)云人物
* 
* @author 清水三千尺
*
*/
public interface IBranch {
  // 能夠增加小兵(樹葉節(jié)點)或者是經(jīng)理(樹枝節(jié)點)
  public void addSubordinate(ICorp corp);

  // 我還要能夠獲得下屬的信息
  public ArrayList<ICorp> getSubordinate();
  
  /*
   * 本來還應(yīng)該有一個方法delSubordinate(ICorp corp),刪除下屬 
   * 這個方法我們沒有用到就不寫進(jìn)來了
   */
}

接口也是很簡單的,下面是實現(xiàn)類:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 這些樹枝節(jié)點也就是這些領(lǐng)導(dǎo)們既要有自己的信息,還要知道自己的下屬情況
* 
* @author 清水三千尺
*
*/
public class Branch implements IBranch, ICorp {
  // 領(lǐng)導(dǎo)也是人,也有名字
  private String name = "";
  // 領(lǐng)導(dǎo)和領(lǐng)導(dǎo)不同,也是職位區(qū)別
  private String position = "";
  // 領(lǐng)導(dǎo)也是拿薪水的
  private int salary = 0;
  // 領(lǐng)導(dǎo)下邊有那些下級領(lǐng)導(dǎo)和小兵
  ArrayList<ICorp> subordinateList = new ArrayList<ICorp>();

  // 通過構(gòu)造函數(shù)傳遞領(lǐng)導(dǎo)的信息
  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;
  }

  // 領(lǐng)導(dǎo)也是人,他也有信息
  public String getInfo() {
      String info = "";
      info = "姓名:" + this.name;
      info = info + "\t職位:" + this.position;
      info = info + "\t薪水:" + this.salary;
      return info;
  }
}

實現(xiàn)類也很簡單,不多說,程序?qū)懙暮貌缓?,就看別人怎么調(diào)用了,我們看 Client.java 程序:

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 組裝這個樹形結(jié)構(gòu),并展示出來
* 
* @author 清水三千尺
*
*/
public class Client {
  public static void main(String[] args) {
      // 首先是組裝一個組織結(jié)構(gòu)出來
      Branch ceo = compositeCorpTree();
      // 首先把CEO的信息打印出來:
      System.out.println(ceo.getInfo());
      // 然后是所有員工信息
      System.out.println(getTreeInfo(ceo));
  }

  // 把整個樹組裝出來
  public static Branch compositeCorpTree() {
      // 首先產(chǎn)生總經(jīng)理CEO
      Branch root = new Branch("王大麻子", "總經(jīng)理", 100000);
      // 把三個部門經(jīng)理產(chǎn)生出來
      Branch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      Branch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      Branch financeDep = new Branch("趙三駝子", "財務(wù)部經(jīng)理", 30000);
      // 再把三個小組長產(chǎn)生出來
      Branch firstDevGroup = new Branch("楊三乜斜", "開發(fā)一組組長", 5000);
      Branch secondDevGroup = new Branch("吳大棒槌", "開發(fā)二組組長", 6000);
      // 把所有的小兵都產(chǎn)生出來
      Leaf a = new Leaf("a", "開發(fā)人員", 2000);
      Leaf b = new Leaf("b", "開發(fā)人員", 2000);
      Leaf c = new Leaf("c", "開發(fā)人員", 2000);
      Leaf d = new Leaf("d", "開發(fā)人員", 2000);
      Leaf e = new Leaf("e", "開發(fā)人員", 2000);
      Leaf f = new Leaf("f", "開發(fā)人員", 2000);
      Leaf g = new Leaf("g", "開發(fā)人員", 2000);
      Leaf h = new Leaf("h", "銷售人員", 5000);
      Leaf i = new Leaf("i", "銷售人員", 4000);
      Leaf j = new Leaf("j", "財務(wù)人員", 5000);
      Leaf k = new Leaf("k", "CEO秘書", 8000);
      Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副經(jīng)理", 20000);
      // 開始組裝
      // CEO下有三個部門經(jīng)理和一個秘書
      root.addSubordinate(k);
      root.addSubordinate(developDep);
      root.addSubordinate(salesDep);
      root.addSubordinate(financeDep);
      // 研發(fā)部經(jīng)理
      developDep.addSubordinate(zhengLaoLiu);
      developDep.addSubordinate(firstDevGroup);
      developDep.addSubordinate(secondDevGroup);
      // 看看開發(fā)兩個開發(fā)小組下有什么
      firstDevGroup.addSubordinate(a);
      firstDevGroup.addSubordinate(b);
      firstDevGroup.addSubordinate(c);
      secondDevGroup.addSubordinate(d);
      secondDevGroup.addSubordinate(e);
      secondDevGroup.addSubordinate(f);
      // 再看銷售部下的人員情況
      salesDep.addSubordinate(h);
      salesDep.addSubordinate(i);
      // 后一個財務(wù)
      financeDep.addSubordinate(j);
      return root;
  }

  // 遍歷整棵樹,只要給我根節(jié)點,我就能遍歷出所有的節(jié)點
  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;
  }

}

運(yùn)行結(jié)果如下:



一個非常清理的樹狀人員資源管理圖出現(xiàn)了,那我們的程序是否還可以優(yōu)化?可以!你看Leaf和Branch中都有 getInfo 信息,是否可以抽象,好,我們抽象一下:



你一看這個圖,樂了,能不樂嘛,減少很多工作量了,接口沒有了,改成抽象類了,IBranch 接口也沒有了,直接把方法放到了實現(xiàn)類中了,那我們先來看抽象類:
package com.qssqc.composite.perfect;

/**
* 定義一個公司的人員的抽象類
* 
* @author 清水三千尺
*
*/
public abstract class Corp {
  // 公司每個人都有名稱
  private String name = "";
  // 公司每個人都職位
  private String position = "";
  // 公司每個人都有薪水
  private int salary = 0;

  /*
   * 通過接口的方式傳遞,我們改變一下習(xí)慣,傳遞進(jìn)來的參數(shù)名以下劃線開始 
   * 這個在一些開源項目中非常常見,一般構(gòu)造函數(shù)都是這么定義的
   */
  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;
  }
}

抽象類嘛,就應(yīng)該抽象出一些共性的東西出來,然后看兩個具體的實現(xiàn)類:

package com.qssqc.composite.perfect;

/**
* 普通員工很簡單,就寫一個構(gòu)造函數(shù)就可以了
* 
* @author 清水三千尺
*
*/
public class Leaf extends Corp {
  // 就寫一個構(gòu)造函數(shù),這個是必須的
  public Leaf(String _name, String _position, int _salary) {
      super(_name, _position, _salary);
  }
}

這個改動比較多,就幾行代碼就完成了,確實就應(yīng)該這樣,下面是小頭目的實現(xiàn)類:

package com.qssqc.composite.perfect;

import java.util.ArrayList;

/**
* 節(jié)點類,也簡單了很多
* 
* @author 清水三千尺
*
*/
public class Branch extends Corp {
  // 領(lǐng)導(dǎo)下邊有那些下級領(lǐng)導(dǎo)和小兵
  ArrayList<Corp> subordinateList = new ArrayList<Corp>();

  // 構(gòu)造函數(shù)是必須的了
  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;

/**
* 組裝這個樹形結(jié)構(gòu),并展示出來
* 
* @author 清水三千尺
*
*/
public class Client {
  public static void main(String[] args) {
      // 首先是組裝一個組織結(jié)構(gòu)出來
      Branch ceo = compositeCorpTree();
      // 首先把CEO的信息打印出來:
      System.out.println(ceo.getInfo());
      // 然后是所有員工信息
      System.out.println(getTreeInfo(ceo));
  }

  // 把整個樹組裝出來
  public static Branch compositeCorpTree() {
      // 首先產(chǎn)生總經(jīng)理CEO
      Branch root = new Branch("王大麻子", "總經(jīng)理", 100000);
      // 把三個部門經(jīng)理產(chǎn)生出來
      Branch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      Branch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      Branch financeDep = new Branch("趙三駝子", "財務(wù)部經(jīng)理", 30000);
      // 再把三個小組長產(chǎn)生出來
      Branch firstDevGroup = new Branch("楊三乜斜", "開發(fā)一組組長", 5000);
      Branch secondDevGroup = new Branch("吳大棒槌", "開發(fā)二組組長", 6000);
      // 把所有的小兵都產(chǎn)生出來
      Leaf a = new Leaf("a", "開發(fā)人員", 2000);
      Leaf b = new Leaf("b", "開發(fā)人員", 2000);
      Leaf c = new Leaf("c", "開發(fā)人員", 2000);
      Leaf d = new Leaf("d", "開發(fā)人員", 2000);
      Leaf e = new Leaf("e", "開發(fā)人員", 2000);
      Leaf f = new Leaf("f", "開發(fā)人員", 2000);
      Leaf g = new Leaf("g", "開發(fā)人員", 2000);
      Leaf h = new Leaf("h", "銷售人員", 5000);
      Leaf i = new Leaf("i", "銷售人員", 4000);
      Leaf j = new Leaf("j", "財務(wù)人員", 5000);
      Leaf k = new Leaf("k", "CEO秘書", 8000);
      Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副經(jīng)理", 20000);
      // 開始組裝
      // CEO下有三個部門經(jīng)理和一個秘書
      root.addSubordinate(k);
      root.addSubordinate(developDep);
      root.addSubordinate(salesDep);
      root.addSubordinate(financeDep);
      // 研發(fā)部經(jīng)理
      developDep.addSubordinate(zhengLaoLiu);
      developDep.addSubordinate(firstDevGroup);
      developDep.addSubordinate(secondDevGroup);
      // 看看開發(fā)兩個開發(fā)小組下有什么
      firstDevGroup.addSubordinate(a);
      firstDevGroup.addSubordinate(b);
      firstDevGroup.addSubordinate(c);
      secondDevGroup.addSubordinate(d);
      secondDevGroup.addSubordinate(e);
      secondDevGroup.addSubordinate(f);
      // 再看銷售部下的人員情況
      salesDep.addSubordinate(h);
      salesDep.addSubordinate(i);
      // 后一個財務(wù)
      financeDep.addSubordinate(j);
      return root;
  }

  // 遍歷整棵樹,只要給我根節(jié)點,我就能遍歷出所有的節(jié)點
  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 抽象類就成了,就上面黃色的部分作了點修改,其他保持不變,運(yùn)行結(jié)果還是保持一樣:



確實是類、接口減少了很多,而且程序也簡單很多,但是大家可能還是很迷茫,這個 Client 程序并沒有改變多少呀,非常正確,樹的組裝你是跑不了的,你要知道在項目中使用數(shù)據(jù)庫來存儲這些信息的,你從數(shù)據(jù)庫中提出出來哪些人要分配到樹枝,哪些人要分配到樹葉,樹枝與樹枝、樹葉的關(guān)系,這些都需要人去定義,通常這里使用一個界面去配置,在數(shù)據(jù)庫中是一個標(biāo)志信息,例如定義這樣一張表:

主鍵 唯一編碼 名稱 是否是葉子節(jié)點 父節(jié)點
1 CEO 總經(jīng)理
2 developDep 研發(fā)部經(jīng)理 CEO
3 salesDep 銷售部經(jīng)理 CEO
4 financeDep 財務(wù)部經(jīng)理 CEO
5 k 總經(jīng)理秘書 CEO
6 a 員工 A developed
7 b 員工 B Developed

從這張表中已經(jīng)定義個一個樹形結(jié)構(gòu),我們要做的就是從數(shù)據(jù)庫中讀取出來,然后展現(xiàn)到前臺上,這個讀取就用個 for 循環(huán)加上遞歸是不是就可以把一棵樹建立起來?我們程序中其實還包涵了數(shù)據(jù)的讀取和加工,用了數(shù)據(jù)庫后,數(shù)據(jù)和邏輯已經(jīng)在表中定義好了,我們直接讀取放到樹上就可以了,這個還是比較容易做了的,大家不妨自己考慮一下。
上面我們講到的就是組合模式(也叫合成模式),有時又叫做部分-整體模式(Part-Whole),主要是用來描述整體與部分的關(guān)系,用的多的地方就是樹形結(jié)構(gòu)。組合模式通用類圖如下:



我們先來說說組合模式的幾個角色:
抽象構(gòu)件角色(Component):定義參加組合的對象的共有方法和屬性,可以定義一些默認(rèn)的行為或?qū)傩裕槐热缥覀兝又械?getInfo 就封裝到了抽象類中。
葉子構(gòu)件(Leaf):葉子對象,其下再也沒有其他的分支。
樹枝構(gòu)件(Composite):樹枝對象,它的作用是組合樹枝節(jié)點和葉子節(jié)點;
組合模式有兩種模式,透明模式和安全模式,這兩個模式有什么區(qū)別呢?先看類圖:


透明模式類圖

安全模式類圖

從類圖上大家應(yīng)該能看清楚了,這兩種模式各有優(yōu)缺點,透明模式是把用來組合使用的方法放到抽象類中,比如add(),remove()以及getChildren等方法(順便說一下,getChildren一般返回的結(jié)果為Iterable的實現(xiàn)類,很多,大家可以看 JDK 的幫助),不管葉子對象還是樹枝對象都有相同的結(jié)構(gòu),通過判斷是getChildren 的返回值確認(rèn)是葉子節(jié)點還是樹枝節(jié)點,如果處理不當(dāng),這個會在運(yùn)行期出現(xiàn)問題的,不是很建議的方式;安全模式就不同了,它是把樹枝節(jié)點和樹葉節(jié)點徹底分開,樹枝節(jié)點單獨擁有用來組合的方法,這種方法比較安全,我們的例子使用了安全模式。
組合模式的優(yōu)點有哪些呢?第一個優(yōu)點只要是樹形結(jié)構(gòu),就要考慮使用組合模式,這個一定記住,只要是要體現(xiàn)局部和整體的關(guān)系的時候,而且這種關(guān)系還可能比較深,考慮一下組合模式吧。組合模式有一個非常明顯的缺點,看到我們在 Client.java 中的的定義了樹葉和樹枝使用時的定義了嗎?如下:

發(fā)現(xiàn)什么問題了嗎?直接使用了實現(xiàn)類!這個在面向接口編程上是很不恰當(dāng)?shù)?,這個在使用的時候要考慮清楚。
組合模式在項目中到處都有,比如現(xiàn)在的頁面結(jié)構(gòu)一般都是上下結(jié)構(gòu),上面放系統(tǒng)的 Logo,下邊分為兩部分:左邊是導(dǎo)航菜單,右邊是展示區(qū),左邊的導(dǎo)航菜單一般都是樹形的結(jié)構(gòu),比較清晰,這個 JavaScript有很多例子,大家可以到網(wǎng)上搜索一把;還有,我們的自己也是一個樹狀結(jié)構(gòu),根據(jù)我,能夠找到我的父母,根據(jù)父親又能找到爺爺奶奶,根據(jù)母親能夠找到外公外婆等等,很典型的樹形結(jié)構(gòu),而且還很規(guī)范(這個要是不規(guī)范那肯定是亂套了)。
我們在上面也還提到了一個問題,就是樹的遍歷問題,從上到下遍歷沒有問題,但是我要是從下往上遍歷呢?比如在人力資源這顆樹上,我從中抽取一個用戶,要找到它的上級有哪些,下級有哪些,怎么處理?想想,~~~,再想想!想出來了吧,我們對下答案,先看類圖:



看類圖中的紅色方框,只要增加兩個方法就可以了,一個是設(shè)置父節(jié)點是誰,一個是查找父節(jié)點是誰,我們來看一下程序的改變:
package com.qssqc.composite.extend;

/**
* 定義一個公司的人員的抽象類
* 
* @author 清水三千尺
*
*/
public abstract class Corp {
  // 公司每個人都有名稱
  private String name = "";
  // 公司每個人都職位
  private String position = "";
  // 公司每個人都有薪水
  private int salary = 0;
  // 父節(jié)點是誰
  private Corp parent = null;

  /*
   * 通過接口的方式傳遞,我們改變一下習(xí)慣,傳遞進(jìn)來的參數(shù)名以下劃線開始 
   * 這個在一些開源項目中非常常見,一般構(gòu)造函數(shù)都是定義的
   */
  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;
  }

  // 設(shè)置父節(jié)點
  protected void setParent(Corp _parent) {
      this.parent = _parent;
  }

  // 得到父節(jié)點
  public Corp getParent() {
      return this.parent;
  }
}

就 增 加 了 黃 色 部 分 , 然 后 我 們 再 來 看 看 Branch.java 的 改 變 :

package com.qssqc.composite.extend;

import java.util.ArrayList;

/**
* 節(jié)點類,也簡單了很多
* 
* @author 清水三千尺
*
*/
public class Branch extends Corp {
  // 領(lǐng)導(dǎo)下邊有那些下級領(lǐng)導(dǎo)和小兵
  ArrayList<Corp> subordinateList = new ArrayList<Corp>();

  // 構(gòu)造函數(shù)是必須的了
  public Branch(String _name, String _position, int _salary) {
      super(_name, _position, _salary);
  }

  // 增加一個下屬,可能是小頭目,也可能是個小兵
  public void addSubordinate(Corp corp) {
      corp.setParent(this); // 設(shè)置父節(jié)點
      this.subordinateList.add(corp);
  }

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

增加了黃色部分,看懂程序了嗎?就是在每個節(jié)點甭管是樹枝節(jié)點還是樹葉節(jié)點,都增加了一個屬性:父節(jié)點對象,這樣在樹枝節(jié)點增加子節(jié)點或葉子的時候設(shè)置父節(jié)點,然后你看整棵樹就除了根節(jié)點外每個節(jié)點都一個父節(jié)點,剩下的事情還不好處理嗎?每個節(jié)點上都有父節(jié)點了,你要往上找,那就找唄!Client程 序 我 就 不 寫 了 , 今 天 已 經(jīng) 拷 貝 的 代 碼 實 在 有 點 多 , 大 家 自 己 考 慮 一 下 , 寫 個 find 方 法 , 然 后 一 個 一 個 往上找,簡單的方法了!
有 了 這 個 parent 屬 性 , 什 么 后 序 遍 歷 ( 從 下 往 上 找 )、中序遍歷(從中間某個環(huán)節(jié)往上或往下遍歷)都解決了,這個就不多說了。

結(jié)束語
代碼結(jié)構(gòu)

再提一個問題,樹葉節(jié)點和樹枝節(jié)點是有順序的,你不能亂排的,怎么辦?比如我們上面的例子,研發(fā)一組下邊有三個成員,這三個成員是要進(jìn)行排序的呀,你怎么處理?問我呀,問你呢,好好想想,以后用到著的!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內(nèi)容

  • 概述 UML類圖 代碼栗子 總結(jié) 概述概念 組合模式是指將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),組合模...
    tanoak閱讀 386評論 0 0
  • 結(jié)構(gòu)型設(shè)計模式 12.組合模式 組合模式又稱“整體-部分”設(shè)計模式,讓整體與部分的使用具有一致性。 12.1創(chuàng)建抽...
    crazyydevil閱讀 140評論 0 0
  • 組合模式將對象組合成樹形結(jié)構(gòu)以表示"部分-整體"的層次結(jié)構(gòu)。組合模式是的用戶對單個對象和組合對象的使用具有一致性。...
    Mitchell閱讀 386評論 0 0
  • 轉(zhuǎn):http://www.runoob.com/design-pattern/composite-pattern....
    right_33cb閱讀 213評論 0 0
  • 類圖: 精髓 樹形結(jié)構(gòu),可以表現(xiàn)出對象的層次結(jié)構(gòu),與部分-整體的關(guān)系 (精髓中的精髓)節(jié)點實現(xiàn)同一個公共接口,使得...
    炫邁哥閱讀 291評論 0 0