組合模式

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



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



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

import java.util.ArrayList;

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

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

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

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

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

package com.qssqc.composite;

import java.util.ArrayList;

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

  // 保存根節(jié)點(diǎn)下的樹枝節(jié)點(diǎn)和樹葉節(jié)點(diǎn)忿墅,Subordinate的意思是下級
  private ArrayList subordinateList = new ArrayList();
  // 根節(jié)點(diǎn)的名稱
  private String name = "";
  // 根節(jié)點(diǎn)的職位
  private String position = "";
  // 根節(jié)點(diǎn)的薪水
  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é)點(diǎn)
  public void add(IBranch branch) {
      this.subordinateList.add(branch);
  }

  // 增加葉子節(jié)點(diǎn)扁藕,比如秘書,直接隸屬于總經(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é)點(diǎn)(部門經(jīng)理)和葉子節(jié)點(diǎn)(秘書)棍弄。我們再來看 IBranch.java:

package com.qssqc.composite;

import java.util.ArrayList;

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

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

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

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

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

package com.qssqc.composite;

import java.util.ArrayList;

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

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

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

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

  // 獲得自己樹枝節(jié)點(diǎn)的信息
  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é)點(diǎn)呼畸,也就是員工的接口:

package com.qssqc.composite;

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

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

package com.qssqc.composite;

/**
* 小的葉子節(jié)點(diǎn)
* 
* @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é)點(diǎn),樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)都已經(jīng)實(shí)現(xiàn)了儒陨,從總經(jīng)理花嘶、部門經(jīng)理到終的員工都已經(jīng)實(shí)現(xiàn)了,然后的工作就是組裝成一個(gè)樹狀結(jié)構(gòu)和遍歷這個(gè)樹狀結(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)生了一個(gè)根節(jié)點(diǎn)
      IRoot ceo = new Root("王大麻子", "總經(jīng)理", 100000);
      // 產(chǎn)生三個(gè)部門經(jīng)理干签,也就是樹枝節(jié)點(diǎn)
      IBranch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      IBranch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      IBranch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);
      // 再把三個(gè)小組長產(chǎn)生出來
      IBranch firstDevGroup = new Branch("楊三乜斜", "開發(fā)一組組長", 5000);
      IBranch secondDevGroup = new Branch("吳大棒槌", "開發(fā)二組組長", 6000);
      // 剩下的及時(shí)我們這些小兵了,就是路人甲津辩,路人乙
      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", "財(cái)務(wù)人員", 5000);
      ILeaf k = new Leaf("k", "CEO秘書", 8000);
      ILeaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副總", 20000);
      // 該產(chǎn)生的人都產(chǎn)生出來了,然后我們怎么組裝這棵樹
      // 首先是定義總經(jīng)理下有三個(gè)部門經(jīng)理
      ceo.add(developDep);
      ceo.add(salesDep);
      ceo.add(financeDep);
      // 總經(jīng)理下還有一個(gè)秘書
      ceo.add(k);
      // 定義研發(fā)部門 下的結(jié)構(gòu)
      developDep.add(firstDevGroup);
      developDep.add(secondDevGroup);
      // 研發(fā)部經(jīng)理下還有一個(gè)副總
      developDep.add(zhengLaoLiu);
      // 看看開發(fā)兩個(gè)開發(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);
      // 后一個(gè)財(cái)務(wù)
      financeDep.add(j);
      // 樹狀結(jié)構(gòu)寫完畢,然后我們打印出來
      System.out.println(ceo.getInfo());
      // 打印出來整個(gè)樹形
      getAllSubordinateInfo(ceo.getSubordinateInfo());
  }

  // 遍歷所有的樹枝節(jié)點(diǎn)喘沿,打印出信息
  private static void getAllSubordinateInfo(ArrayList subordinateList) {
      int length = subordinateList.size();
      for (int m = 0; m < length; m++) {
          // 定義一個(gè)ArrayList長度闸度,不要在for循環(huán)中每次計(jì)算
          Object s = subordinateList.get(m);
          if (s instanceof Leaf) {
              // 是個(gè)葉子節(jié)點(diǎn),也就是員工
              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());
          }
      }
  }

}

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



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



這個(gè)類圖還是有點(diǎn)問題的扛吞,接口的作用是什么?定義共性荆责,那 ILeaf 和 IBranch 是不是也有共性呢滥比?有 getInfo(),我們是不是要把這個(gè)共性也已經(jīng)封裝起來呢做院?好盲泛,我們再修改一下類圖:

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

package com.qssqc.composite.advance;

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

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

package com.qssqc.composite.advance;

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

  // 通過一個(gè)構(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)理和小組長是怎么實(shí)現(xiàn)的囤捻,先看接口:

package com.qssqc.composite.advance;

import java.util.ArrayList;

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

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

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

package com.qssqc.composite.advance;

import java.util.ArrayList;

/**
* 這些樹枝節(jié)點(diǎn)也就是這些領(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;
  }

  // 增加一個(gè)下屬誊涯,可能是小頭目,也可能是個(gè)小兵
  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;
  }
}

實(shí)現(xiàn)類也很簡單暴构,不多說,程序?qū)懙暮貌缓枚文ィ涂磩e人怎么調(diào)用了取逾,我們看 Client.java 程序:

package com.qssqc.composite.advance;

import java.util.ArrayList;

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

  // 把整個(gè)樹組裝出來
  public static Branch compositeCorpTree() {
      // 首先產(chǎn)生總經(jīng)理CEO
      Branch root = new Branch("王大麻子", "總經(jīng)理", 100000);
      // 把三個(gè)部門經(jīng)理產(chǎn)生出來
      Branch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      Branch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      Branch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);
      // 再把三個(gè)小組長產(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", "財(cái)務(wù)人員", 5000);
      Leaf k = new Leaf("k", "CEO秘書", 8000);
      Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副經(jīng)理", 20000);
      // 開始組裝
      // CEO下有三個(gè)部門經(jīng)理和一個(gè)秘書
      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ā)兩個(gè)開發(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);
      // 后一個(gè)財(cái)務(wù)
      financeDep.addSubordinate(j);
      return root;
  }

  // 遍歷整棵樹,只要給我根節(jié)點(diǎn)苹支,我就能遍歷出所有的節(jié)點(diǎn)
  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 {
              // 是個(gè)小頭目
              info = info + s.getInfo() + "\n" + getTreeInfo((Branch) s);
          }
      }
      return info;
  }

}

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



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



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

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

  /*
   * 通過接口的方式傳遞捷枯,我們改變一下習(xí)慣滚秩,傳遞進(jìn)來的參數(shù)名以下劃線開始 
   * 這個(gè)在一些開源項(xiàng)目中非常常見,一般構(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)該抽象出一些共性的東西出來郁油,然后看兩個(gè)具體的實(shí)現(xiàn)類:

package com.qssqc.composite.perfect;

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

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

package com.qssqc.composite.perfect;

import java.util.ArrayList;

/**
* 節(jié)點(diǎn)類案站,也簡單了很多
* 
* @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);
  }

  // 增加一個(gè)下屬,可能是小頭目棘街,也可能是個(gè)小兵
  public void addSubordinate(Corp corp) {
      this.subordinateList.add(corp);
  }

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

也縮減了很多蟆盐,再看 Client.java 程序,這個(gè)就沒有多大變化了:

package com.qssqc.composite.perfect;

import java.util.ArrayList;

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

  // 把整個(gè)樹組裝出來
  public static Branch compositeCorpTree() {
      // 首先產(chǎn)生總經(jīng)理CEO
      Branch root = new Branch("王大麻子", "總經(jīng)理", 100000);
      // 把三個(gè)部門經(jīng)理產(chǎn)生出來
      Branch developDep = new Branch("劉大瘸子", "研發(fā)部門經(jīng)理", 10000);
      Branch salesDep = new Branch("馬二拐子", "銷售部門經(jīng)理", 20000);
      Branch financeDep = new Branch("趙三駝子", "財(cái)務(wù)部經(jīng)理", 30000);
      // 再把三個(gè)小組長產(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", "財(cái)務(wù)人員", 5000);
      Leaf k = new Leaf("k", "CEO秘書", 8000);
      Leaf zhengLaoLiu = new Leaf("鄭老六", "研發(fā)部副經(jīng)理", 20000);
      // 開始組裝
      // CEO下有三個(gè)部門經(jīng)理和一個(gè)秘書
      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ā)兩個(gè)開發(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);
      // 后一個(gè)財(cái)務(wù)
      financeDep.addSubordinate(j);
      return root;
  }

  // 遍歷整棵樹,只要給我根節(jié)點(diǎn)石挂,我就能遍歷出所有的節(jié)點(diǎn)
  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 {
              // 是個(gè)小頭目
              info = info + s.getInfo() + "\n" + getTreeInfo((Branch) s);
          }
      }
      return info;
  }

}

就是把用到 ICorp 接口的地方修改為 Corp 抽象類就成了,就上面黃色的部分作了點(diǎn)修改险污,其他保持不變誊稚,運(yùn)行結(jié)果還是保持一樣:



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

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

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



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


透明模式類圖

安全模式類圖

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

發(fā)現(xiàn)什么問題了嗎?直接使用了實(shí)現(xiàn)類溉跃!這個(gè)在面向接口編程上是很不恰當(dāng)?shù)拇迮伲@個(gè)在使用的時(shí)候要考慮清楚。
組合模式在項(xiàng)目中到處都有撰茎,比如現(xiàn)在的頁面結(jié)構(gòu)一般都是上下結(jié)構(gòu)嵌牺,上面放系統(tǒng)的 Logo,下邊分為兩部分:左邊是導(dǎo)航菜單龄糊,右邊是展示區(qū)逆粹,左邊的導(dǎo)航菜單一般都是樹形的結(jié)構(gòu),比較清晰炫惩,這個(gè) JavaScript有很多例子僻弹,大家可以到網(wǎng)上搜索一把;還有他嚷,我們的自己也是一個(gè)樹狀結(jié)構(gòu)蹋绽,根據(jù)我,能夠找到我的父母筋蓖,根據(jù)父親又能找到爺爺奶奶卸耘,根據(jù)母親能夠找到外公外婆等等,很典型的樹形結(jié)構(gòu)粘咖,而且還很規(guī)范(這個(gè)要是不規(guī)范那肯定是亂套了)鹊奖。
我們在上面也還提到了一個(gè)問題,就是樹的遍歷問題涂炎,從上到下遍歷沒有問題忠聚,但是我要是從下往上遍歷呢设哗?比如在人力資源這顆樹上,我從中抽取一個(gè)用戶两蟀,要找到它的上級有哪些网梢,下級有哪些,怎么處理赂毯?想想战虏,~~~,再想想党涕!想出來了吧烦感,我們對下答案,先看類圖:



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

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

  /*
   * 通過接口的方式傳遞绿渣,我們改變一下習(xí)慣,傳遞進(jìn)來的參數(shù)名以下劃線開始 
   * 這個(gè)在一些開源項(xiàng)目中非常常見燕耿,一般構(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é)點(diǎn)
  protected void setParent(Corp _parent) {
      this.parent = _parent;
  }

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

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

package com.qssqc.composite.extend;

import java.util.ArrayList;

/**
* 節(jié)點(diǎn)類,也簡單了很多
* 
* @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);
  }

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

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

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

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

再提一個(gè)問題戈稿,樹葉節(jié)點(diǎn)和樹枝節(jié)點(diǎn)是有順序的西土,你不能亂排的,怎么辦鞍盗?比如我們上面的例子需了,研發(fā)一組下邊有三個(gè)成員,這三個(gè)成員是要進(jìn)行排序的呀般甲,你怎么處理肋乍?問我呀,問你呢敷存,好好想想墓造,以后用到著的!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末历帚,一起剝皮案震驚了整個(gè)濱河市滔岳,隨后出現(xiàn)的幾起案子杠娱,更是在濱河造成了極大的恐慌挽牢,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摊求,死亡現(xiàn)場離奇詭異禽拔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)室叉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門睹栖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人茧痕,你說我怎么就攤上這事野来。” “怎么了踪旷?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵曼氛,是天一觀的道長。 經(jīng)常有香客問我令野,道長舀患,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任气破,我火速辦了婚禮聊浅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己低匙,他們只是感情好旷痕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著努咐,像睡著了一般苦蒿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渗稍,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天佩迟,我揣著相機(jī)與錄音,去河邊找鬼竿屹。 笑死报强,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拱燃。 我是一名探鬼主播秉溉,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼碗誉!你這毒婦竟也來了召嘶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤哮缺,失蹤者是張志新(化名)和其女友劉穎弄跌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尝苇,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铛只,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了糠溜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淳玩。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖非竿,靈堂內(nèi)的尸體忽然破棺而出蜕着,到底是詐尸還是另有隱情,我是刑警寧澤红柱,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布承匣,位于F島的核電站,受9級特大地震影響豹芯,放射性物質(zhì)發(fā)生泄漏悄雅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一铁蹈、第九天 我趴在偏房一處隱蔽的房頂上張望宽闲。 院中可真熱鬧众眨,春花似錦、人聲如沸容诬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽览徒。三九已至狈定,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間习蓬,已是汗流浹背纽什。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躲叼,地道東北人芦缰。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像枫慷,于是被迫代替她去往敵國和親让蕾。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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

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