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