「補課」進行時:設計模式(14)——組合模式

1. 前文匯總
2. 某東的菜單
前段時間雙十一,不知道各位的戰(zhàn)果如何,反正我是屯了兩盒口罩湊個數(shù)。
電商平臺為我們提供的方便快捷的搜索框入口,我想大多數(shù)人在使用的時候應該都會使用這個入口,但其實電商平臺還為我們提供了另一個入口,就是它的分類體系,如下:

我簡單抽象一下:
-?服裝
????-?男裝
????????-?襯衣
????????-?夾克
????-?女裝
????????-?裙子
????????-?套裝
可以看到,這是一個樹結構,在前端實現(xiàn)一個這種菜單樹可以選用 ZTree 插件,做過前端的都知道。
下面,用 Java 代碼實現(xiàn)一下,輸出一下上面的這個樹狀結構:
觀察這個樹狀結構,可以看到節(jié)點分為三種類型:
根節(jié)點 樹枝節(jié)點 葉子節(jié)點(沒有子節(jié)點)
根節(jié)點和樹枝節(jié)點的構造是比較類似的,都是可以有子節(jié)點,這兩個節(jié)點可以抽象成一個對象。
首先定義一個葉子節(jié)點:
public?class?Leaf?{
????//?葉子對象的名字
????private?String?name;
????//?構造方法
????public?Leaf(String?name)?{
????????this.name?=?name;
????}
????//?輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
????public?void?printStruct(String?preStr)?{
????????System.out.println(preStr?+?"?-?"?+?name);
????}
}
接著定義一個組合對象:
public?class?Composite?{
????//?組合對象集合
????private?Collection?childComposite?=?new?ArrayList<>();
????//?葉子對象集合
????private?Collection?childLeaf?=?new?ArrayList<>();
????//?組合對象名稱
????private?String?name;
????//?構造函數(shù)
????public?Composite(String?name)?{
????????this.name?=?name;
????}
????//?向組合對象加入被它包含的其它組合對象
????public?void?addComposite(Composite?c){
????????this.childComposite.add(c);
????}
????//?向組合對象加入被它包含的葉子對象
????public?void?addLeaf(Leaf?leaf){
????????this.childLeaf.add(leaf);
????}
????//?輸出自身結構
????public?void?printStruct(String?preStr){
????????System.out.println(preStr?+?"?+?"?+?this.name);
????????preStr+="?";
????????for(Leaf?leaf?:?childLeaf){
????????????leaf.printStruct(preStr);
????????}
????????for(Composite?c?:?childComposite){
????????????c.printStruct(preStr);
????????}
????}
}
來一個測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//定義所有的組合對象
????????Composite?root?=?new?Composite("服裝");
????????Composite?c1?=?new?Composite("男裝");
????????Composite?c2?=?new?Composite("女裝");
????????//定義所有的葉子對象
????????Leaf?leaf1?=?new?Leaf("襯衣");
????????Leaf?leaf2?=?new?Leaf("夾克");
????????Leaf?leaf3?=?new?Leaf("裙子");
????????Leaf?leaf4?=?new?Leaf("套裝");
????????//按照樹的結構來組合組合對象和葉子對象
????????root.addComposite(c1);
????????root.addComposite(c2);
????????c1.addLeaf(leaf1);
????????c1.addLeaf(leaf2);
????????c2.addLeaf(leaf3);
????????c2.addLeaf(leaf4);
????????//調(diào)用根對象的輸出功能來輸出整棵樹
????????root.printStruct("");
????}
}
上面的這種實現(xiàn)方案,雖然能實現(xiàn)了我們希望看到的功能,但是有一個很明顯的問題:那就是必須區(qū)分組合對象和葉子對象,并進行有區(qū)別的對待。
3. 組合模式
3.1 定義
組合模式(Composite Pattern)也叫合成模式,有時又叫做部分-整體模式(Part-Whole),主要是用來描述部分與整體的關系,其定義如下:
Compose objects into tree structures to represent part-wholehierarchies.Composite lets clients treat individual objects and compositionsof objects uniformly.(將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。)
3.2 通用類圖

Component 抽象構件角色: 定義參加組合對象的共有方法和屬性,可以定義一些默認的行為或屬性。 Leaf 葉子構件: 葉子對象,其下再也沒有其他的分支,也就是遍歷的最小單位。 Composite 樹枝構件: 樹枝對象,它的作用是組合樹枝節(jié)點和葉子節(jié)點形成一個樹形結構。
3.3 通用代碼
public?abstract?class?Component?{
????//?整體和個體都共享的邏輯
????void?doSomething()?{
????????//?具體的業(yè)務邏輯
????}
}
public?class?Leaf?extends?Component?{
????//?可以復寫父類的方法
????@Override
????void?doSomething()?{
????????super.doSomething();
????}
}
public?class?Composite?extends?Component?{
????//?構建容器
????private?ArrayList?componentArrayList?=?new?ArrayList<>();
????//?增加一個葉子構件或者樹枝構件
????public?void?add(Component?component)?{
????????this.componentArrayList.add(component);
????}
????//?刪除一個葉子構件或者樹枝構件
????public?void?remove(Component?component)?{
????????this.componentArrayList.remove(component);
????}
????//?獲得分支下所有葉子構件或者樹枝構件
????public?ArrayList?getChildren()? {
????????return?this.componentArrayList;
????}
}
3.4 優(yōu)點
高層模塊調(diào)用簡單:
一棵樹形機構中的所有節(jié)點都是 Component ,局部和整體對調(diào)用者來說沒有任何區(qū)別,也就是說,高層模塊不必關心自己處理的是單個對象還是整個組合結構,簡化了高層模塊的代碼。
節(jié)點自由增加:
使用了組合模式后,我們可以看看,如果想增加一個樹枝節(jié)點、樹葉節(jié)點是不是都很容易,只要找到它的父節(jié)點就成,非常容易擴展,符合開閉原則,對以后的維護非常有利。
4. 示例改進(安全模式)
把最上面的示例修改成組合模式,首先需要定義一個抽象組件:
public?abstract?class?Component?{
????//?輸出組件名稱
????abstract?void?printStruct(String?preStr);
}
葉子節(jié)點:
public?class?Leaf?extends?Component?{
????private?String?name;
????public?Leaf(String?name)?{
????????this.name?=?name;
????}
????@Override
????void?printStruct(String?preStr)?{
????????System.out.println(preStr?+?"?-?"?+?name);
????}
}
樹枝節(jié)點:
public?class?Composite?extends?Component{
????//?組合對象集合
????private?Collection?childComponents;
????//?組合對象的名字
????private?String?name;
????public?Composite(String?name)?{
????????this.name?=?name;
????}
????public?void?addChild(Component?child)?{
????????if?(this.childComponents?==?null)?{
????????????this.childComponents?=?new?ArrayList<>();
????????}
????????this.childComponents.add(child);
????}
????void?removeChild(Component?child)?{
????????this.childComponents.remove(child);
????}
????Collection?getChildren()?{
????????return?this.childComponents;
????}
????@Override
????void?printStruct(String?preStr)?{
????????System.out.println(preStr?+?"?+?"?+?this.name);
????????if?(this.childComponents?!=?null)?{
????????????preStr+="?";
????????????for(Component?c?:?this.childComponents){
????????????????//遞歸輸出每個子對象
????????????????c.printStruct(preStr);
????????????}
????????}
????}
}
測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//?定義根節(jié)點
????????Composite?root?=?new?Composite("服裝");
????????//?創(chuàng)建兩個樹枝節(jié)點
????????Composite?c1?=?new?Composite("男裝");
????????Composite?c2?=?new?Composite("女裝");
????????//?定義所有的葉子對象
????????Leaf?leaf1?=?new?Leaf("襯衣");
????????Leaf?leaf2?=?new?Leaf("夾克");
????????Leaf?leaf3?=?new?Leaf("裙子");
????????Leaf?leaf4?=?new?Leaf("套裝");
????????//?按照樹的結構來組合組合對象和葉子對象
????????root.addChild(c1);
????????root.addChild(c2);
????????c1.addChild(leaf1);
????????c1.addChild(leaf2);
????????c2.addChild(leaf3);
????????c2.addChild(leaf4);
????????//?調(diào)用根對象的輸出功能來輸出整棵樹
????????root.printStruct("");
????}
}
5. 透明模式
組合模式有兩種不同的實現(xiàn),安全模式和透明模式,上面的示例是安全模式,下面是透明模式的通用類圖:

和上面的那個安全模式的通用類圖對比一下區(qū)別,就非常明顯了,只是單純的把幾個方法 addChild() 、 removeChild() 、 getChildren() 幾個方法放到了抽象類中。
在透明模式中不管葉子對象還是樹枝對象都有相同的結構,通過判斷是否存在子節(jié)點來判斷葉子節(jié)點還是樹枝節(jié)點,如果處理不當,這個會在運行期出現(xiàn)問題。
而在安全模式中,它是把樹枝節(jié)點和樹葉節(jié)點徹底分開,樹枝節(jié)點單獨擁有用來組合的方法,這種方案比較安全。
抽象角色:
public?abstract?class?Component?{
????//?輸出組件名稱
????abstract?void?printStruct(String?preStr);
????//?向組合對象中加入組件對象
????abstract?void?addChild(Component?child);
????//?從組合對象中移出某個組件對象
????abstract?void?removeChild(Component?child);
????//?返回組件對象
????abstract?Collection?getChildren();
}
葉子節(jié)點:
public?class?Leaf?extends?Component?{
????private?String?name;
????public?Leaf(String?name)?{
????????this.name?=?name;
????}
????//?向組合對象中加入組件對象
????@Deprecated
????public?void?addChild(Component?child)?{
????????//?缺省實現(xiàn),如果子類未實現(xiàn)此功能,由父類拋出異常
????????throw?new?UnsupportedOperationException("對象不支持這個功能");
????}
????//?從組合對象中移出某個組件對象
????@Deprecated
????public?void?removeChild(Component?child){
????????//?缺省實現(xiàn),如果子類未實現(xiàn)此功能,由父類拋出異常
????????throw?new?UnsupportedOperationException("對象不支持這個功能");
????}
????@Deprecated
????Collection?getChildren()?{
????????throw?new?UnsupportedOperationException("對象不支持這個功能");
????}
????@Override
????void?printStruct(String?preStr)?{
????????System.out.println(preStr?+?"?-?"?+?name);
????}
}
這里使用 Deprecated 注解是為了在編譯期告訴調(diào)用者,這方法我有,可以調(diào)用,但是已經(jīng)失效了,如果一定要調(diào)用,那么在運行期會拋出 UnsupportedOperationException 的錯誤。
樹枝節(jié)點:
public?class?Composite?extends?Component?{
????//?組合對象集合
????private?Collection?childComponents;
????//?組合對象的名字
????private?String?name;
????public?Composite(String?name)?{
????????this.name?=?name;
????}
????public?void?addChild(Component?child)?{
????????if?(this.childComponents?==?null)?{
????????????this.childComponents?=?new?ArrayList<>();
????????}
????????this.childComponents.add(child);
????}
????@Override
????void?removeChild(Component?child)?{
????????this.childComponents.remove(child);
????}
????@Override
????Collection?getChildren()?{
????????return?this.childComponents;
????}
????@Override
????void?printStruct(String?preStr)?{
????????System.out.println(preStr?+?"?+?"?+?this.name);
????????if?(this.childComponents?!=?null)?{
????????????preStr+="?";
????????????for(Component?c?:?this.childComponents){
????????????????//遞歸輸出每個子對象
????????????????c.printStruct(preStr);
????????????}
????????}
????}
}
測試類:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????//定義所有的組合對象
????????Component?root?=?new?Composite("服裝");
????????Component?c1?=?new?Composite("男裝");
????????Component?c2?=?new?Composite("女裝");
????????//定義所有的葉子對象
????????Component?leaf1?=?new?Leaf("襯衣");
????????Component?leaf2?=?new?Leaf("夾克");
????????Component?leaf3?=?new?Leaf("裙子");
????????Component?leaf4?=?new?Leaf("套裝");
????????//按照樹的結構來組合組合對象和葉子對象
????????root.addChild(c1);
????????root.addChild(c2);
????????c1.addChild(leaf1);
????????c1.addChild(leaf2);
????????c2.addChild(leaf3);
????????c2.addChild(leaf4);
????????//調(diào)用根對象的輸出功能來輸出整棵樹
????????root.printStruct("");
????}
}
6. 參考
https://www.jianshu.com/p/dead42334033

