大家在上學(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)行排序的呀,你怎么處理?問我呀,問你呢,好好想想,以后用到著的!