21.4 組合模式的擴展
21.4.1 真實的組合模式
什么是真實的組合模式?就是你在實際項目中使用的組合模式,而不是僅僅依照書本上學習到的模式,它是“實踐出真知”。在我們的例子中,經過精簡后,確實是類、接口減少了很多,而且程序也簡單很多,但是大家可能還是很迷茫,這個Client程序并沒有改變多少呀,非常正確,樹的組裝是跑不了的,你要知道在項目中使用關系型數據庫來存儲這些信息,你可以從數據庫中直接提取出哪些人要分配到樹枝,哪些人要分配到樹葉,樹枝與樹枝、樹葉的關系等,這些都是由相關的業務人員維護到數據庫中的,通常這里是把數據存放到一張單獨的表中,表結構如圖21-7所示。

圖21-7 關系數據庫中存儲的樹形結構
這張數據表定義了一個樹形結構,我們要做的就是從數據庫中把它讀取出來,然后展現到前臺上,用for循環加上遞歸就可以完成這個讀取。用了數據庫后,數據和邏輯已經在表中定義好了,我們直接讀取放到樹上就可以了,這個還是比較容易做的,大家不妨自己考慮一下。
這才是組合模式的真實引用,它依靠了關系數據庫的非對象存儲性能,非常方便地保存了一個樹形結構。大家可以在項目中考慮采用,想想看現在還有哪個項目不使用關系型數據庫呢?
21.4.2 透明的組合模式
組合模式有兩種不同的實現:透明模式和安全模式,我們上面講的就是安全模式,那透明模式是什么樣子呢?透明模式的通用類圖,如圖21-8所示。

圖21-8 透明模式的通用類圖
我們與圖21-6所示的安全模式類圖對比一下就非常清楚了,透明模式是把用來組合使用的方法放到抽象類中,比如add()、remove()以及getChildren等方法(順便說一下,getChildren一般返回的結果為Iterable的實現類,很多,大家可以看JDK的幫助),不管葉子對象還是樹枝對象都有相同的結構,通過判斷是getChildren的返回值確認是葉子節點還是樹枝節點,如果處理不當,這個會在運行期出現問題,不是很建議的方式;安全模式就不同了,它是把樹枝節點和樹葉節點徹底分開,樹枝節點單獨擁有用來組合的方法,這種方法比較安全,我們的例子使用了安全模式。
由于透明模式的使用者還是比較多,我們也把它的通用源代碼共享出來,首先看抽象構件,如代碼清單21-22所示。
代碼清單21-22 抽象構件
public?abstract?class?Component?{
?????//個體和整體都具有的共享
?????public?void?doSomething(){
?????????????//編寫業務邏輯
?????}
?????//增加一個葉子構件或樹枝構件
?????public?abstract?void?add(Component?component);
?????//刪除一個葉子構件或樹枝構件
?????public?abstract?void?remove(Component?component);
?????//獲得分支下的所有葉子構件和樹枝構件
?????public?abstract?ArrayList<Component>?getChildren();
}
抽象構件定義了樹枝節點和樹葉節點都必須具有的方法和屬性,這樣樹枝節點的實現就不需要任何變化,如代碼清單21-19所示。
樹葉節點繼承了Component抽象類,不想讓它改變有點難,它必須實現三個抽象方法,怎么辦?好辦,給個空方法,如代碼清單21-23所示。
代碼清單21-23 樹葉節點
public?class?Leaf?extends?Component?{
?????@Deprecated
?????public?void?add(Component?component)?throws?UnsupportedOperationException{
?????????????//空實現,直接拋棄一個"不支持請求"異常
?????????????throw?new?UnsupportedOperationException();
?????}
?????@Deprecated
?????public?void?remove(Component?component)throws?UnsupportedOperationException{
?????????????//空實現
?????????????throw?new?UnsupportedOperationException();
?????}
?????@Deprecated
?????public?ArrayList<Component>?getChildren()throws?UnsupportedOperationException{
?????????????//空實現
?????????????throw?new?UnsupportedOperationException();??????????
?????}
}
為什么要加個Deprecated注解呢?就是在編譯器期告訴調用者,你可以調我這個方法,但是可能出現錯誤哦,我已經告訴你“該方法已經失效”了,你還使用那在運行期也會拋出UnsupportedOperationException異常。
在透明模式下,遍歷整個樹形結構是比較容易的,不用進行強制類型轉換,如代碼清單21-24所示。
代碼清單21-24 樹結構遍歷
public?class?Client?{
?????//通過遞歸遍歷樹
?????public?static?void?display(Component?root){
?????????????for(Component?c:root.getChildren()){
??????????????????if(c?instanceof?Leaf){?//葉子節點
??????????????????????????c.doSomething();
??????????????????}else{?//樹枝節點
??????????????????????????display(c);
??????????????????}
??????????}
?????}
}
僅僅在遍歷時不再進行牽制的類型轉化了,其他的組裝則沒有任何變化。透明模式的好處就是它基本遵循了依賴倒轉原則,方便系統進行擴展。
21.4.3 組合模式的遍歷
我們在上面也還提到了一個問題,就是樹的遍歷問題,從上到下遍歷沒有問題,但是我要是從下往上遍歷呢?比如組織機構這棵樹,我從中抽取一個用戶,要找到它的上級有哪些,下級有哪些,怎么處理?想想,再想想!想出來了吧,我們對下答案,類圖如圖21-9所示。

圖21-9 增加父查詢的類圖
看類圖中,在Corp類中增加了兩個方法,setParent是設置父節點是誰,getParent是查找父節點是誰,我們來看一下程序的改變,如代碼清單21-25所示。
代碼清單21-25 抽象構件
public?abstract?class?Corp?{
?????//公司每個人都有名稱
?????private?String?name?=?"";
?????//公司每個人都職位
?????private?String?position?=?"";
?????//公司每個人都有薪水
?????private?int?salary?=0;
?????//父節點是誰
?????private?Corp?parent?=?null;
?????public?Corp(String?_name,String?_position,int?_salary){
?????????????this.name?=?_name;
?????????????this.position?=?_position;
?????????????this.salary?=?_salary;
?????}
?????//獲得員工信息
?????public?String?getInfo(){
?????????????String?info?=?"";
?????????????info?=?"姓名:"?+?this.name;
?????????????info?=?info?+?"\t職位:"+?this.position;
?????????????info?=?info?+?"\t薪水:"?+?this.salary;
?????????????return?info;
?????}
?????//設置父節點
?????protected?void?setParent(Corp?_parent){
?????????????this.parent?=?_parent;
?????}
?????//得到父節點
?????public?Corp?getParent(){
?????????????return?this.parent;
?????}
}
就增加了粗體部分,然后我們再來看看樹枝節點的改變,如代碼清單21-26所示。
代碼清單21-26 樹枝構件
public?class?Branch?extends?Corp?{
?????//領導下邊有哪些下級領導和小兵
?????ArrayList<Corp>?subordinateList?=?new?ArrayList<Corp>();
?????//構造函數是必需的
?????public?Branch(String?_name,String?_position,int?_salary){
?????????????super(_name,_position,_salary);
?????}
?????//增加一個下屬,可能是小頭目,也可能是個小兵
?????public?void?addSubordinate(Corp?corp)?{
?????????????corp.setParent(this);?//設置父節點
?????????????this.subordinateList.add(corp);
?????}
?????//我有哪些下屬
?????public?ArrayList<Corp>?getSubordinate()?{
?????????????return?this.subordinateList;
?????}
}
增加了粗體部分。看懂程序了嗎?甭管是樹枝節點還是樹葉節點,在每個節點都增加了一個屬性:父節點對象,這樣在樹枝節點增加子節點或葉子節點是設置父節點,然后你看整棵樹除了根節點外每個節點都有一個父節點,剩下的事情還不好處理嗎?每個節點上都有父節點了,你要往上找,那就找唄!大家自己考慮一下,寫個find方法,然后一步一步往上找,非常簡單的方法,這里就不再贅述。
有了這個parent屬性,什么后序遍歷(從下往上找)、中序遍歷(從中間某個環節往上或往下遍歷)都解決了,這個就不多說了。
再提一個問題,樹葉節點和樹枝節點是有順序的,你不能亂排,怎么辦?比如我們上面的例子,研發一組下邊有3個成員,這3個成員要進行排序(在機關里這叫做排位,同樣是同事也有個先后升遷順序),你怎么處理?問我呀,問你呢,好好想想,以后用得著的!
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖