「設計模式」組合模式詳解

前言

《設計模式自習室》系列,顧名思義,本系列文章帶你溫習常見的設計模式。主要內容有:

  • 該模式的介紹,包括: 引子、意圖(大白話解釋) 類圖、時序圖(理論規範)
  • 該模式的代碼示例:熟悉該模式的代碼長什麼樣子
  • 該模式的優缺點:模式不是萬金油,不可以濫用模式
  • 該模式的應用案例:瞭解它在哪些重要的源碼中被使用

該系列會逐步更新於我的博客和公眾號(博客見文章底部),也希望各位觀眾老爺能夠關注我的個人公眾號:後端技術漫談,不會錯過精彩好看的文章。

系列文章回顧

  • 【設計模式自習室】開篇:為什麼我們要用設計模式?
  • 【設計模式自習室】建造者模式
  • 【設計模式自習室】原型模式
  • 【設計模式自習室】透徹理解單例模式
  • 【設計模式自習室】理解工廠模式的三種形式
  • 【設計模式自習室】適配器模式
  • 【設計模式自習室】裝飾模式
  • 【設計模式自習室】橋接模式 Bridge Pattern:處理多維度變化
  • 【設計模式自習室】門面模式 Facade Pattern
  • 【設計模式自習室】享元模式 Flyweight Pattern:減少對象數量
  • 【設計模式自習室】詳解代理模式

結構型——組合模式 Composite

引子

組合模式是為了表示那些層次結構,同時部分和整體也可能是一樣的結構,常見的如文件夾或者樹。

「設計模式」組合模式詳解

上圖來自:

https://www.cnblogs.com/betterboyz/p/9356458.html

從上圖可以看出,文件系統是一個樹結構,樹上長有節點。樹的節點有兩種,一種是樹枝節點,即目錄,有內部樹結構,在圖中塗有顏色;另一種是文件,即樹葉節點,沒有內部樹結構。

定義

組合模式定義瞭如何將容器對象和葉子對象進行遞歸組合,使得客戶在使用的過程中無須進行區分,可以對他們進行一致的處理。

在使用組合模式中需要注意幾點也是組合模式最關鍵的地方:葉子對象和組合對象實現相同的接口。這就是組合模式能夠將葉子節點和對象節點進行一致處理的原因。

合成模式的實現根據所實現接口的區別分為兩種形式,分別稱為安全式和透明式,將在類圖一節中詳細介紹兩種形式。

類圖

安全式合成模式

安全模式的合成模式要求管理聚集的方法只出現在樹枝構件類中,而不出現在樹葉構件類中。

  • Component 抽象構件:組合中的對象聲明接口,在適當的情況下,實現所有類共有接口的默認行為。聲明一個接口用於訪問和管理Component子部件。
  • Composite 樹枝構件:是組合中的分支節點對象,它有子節點。樹枝構件類給出所有的管理子對象的方法,如add()、remove()以及getChild()。
  • Leaf 樹葉構件:葉子對象,葉子結點沒有子結點。
「設計模式」組合模式詳解

透明式合成模式

與安全式的合成模式不同的是,透明式的合成模式要求所有的具體構件類,不論樹枝構件還是樹葉構件,均符合一個固定接口。

「設計模式」組合模式詳解

代碼實現

安全式合成模式

  1. 抽象構件角色類 Component
<code>publicinterfaceComponent{
/**
*輸出組建自身的名稱
*/
publicvoidprintStruct(StringpreStr);
}/<code>
  1. 樹枝構件角色類 Composite
<code>publicclassCompositeimplementsComponent{
/**
*用來存儲組合對象中包含的子組件對象
*/
privateList<component>childComponents=newArrayList<component>();
/**
*組合對象的名字
*/
privateStringname;
/**
*構造方法,傳入組合對象的名字
*@paramname組合對象的名字
*/
publicComposite(Stringname){
this.name=name;
}
/**
*聚集管理方法,增加一個子構件對象
*@paramchild子構件對象
*/
publicvoidaddChild(Componentchild){
childComponents.add(child);
}
/**
*聚集管理方法,刪除一個子構件對象
*@paramindex子構件對象的下標

*/
publicvoidremoveChild(intindex){
childComponents.remove(index);
}
/**
*聚集管理方法,返回所有子構件對象
*/
publicList<component>getChild(){
returnchildComponents;
}
/**
*輸出對象的自身結構
*@parampreStr前綴,主要是按照層級拼接空格,實現向後縮進
*/
@Override
publicvoidprintStruct(StringpreStr){
//先把自己輸出
System.out.println(preStr+"+"+this.name);
//如果還包含有子組件,那麼就輸出這些子組件對象
if(this.childComponents!=null){
//添加兩個空格,表示向後縮進兩個空格
preStr+="";
//輸出當前對象的子對象
for(Componentc:childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}

}

}/<component>/<component>/<component>/<code>
  1. 樹葉構件角色類 Leaf
<code>publicclassLeafimplementsComponent{
/**

*葉子對象的名字
*/
privateStringname;
/**
*構造方法,傳入葉子對象的名稱
*@paramname葉子對象的名字
*/
publicLeaf(Stringname){
this.name=name;
}
/**
*輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
*@parampreStr前綴,主要是按照層級拼接的空格,實現向後縮進
*/
@Override
publicvoidprintStruct(StringpreStr){
//TODOAuto-generatedmethodstub
System.out.println(preStr+"-"+name);
}

}/<code>

客戶端調用:

<code>publicclassClient{
publicstaticvoidmain(String[]args){
Compositeroot=newComposite("服裝");
Compositec1=newComposite("男裝");
Compositec2=newComposite("女裝");

Leafleaf1=newLeaf("襯衫");
Leafleaf2=newLeaf("夾克");
Leafleaf3=newLeaf("裙子");
Leafleaf4=newLeaf("套裝");

root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);

c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);

root.printStruct("");
}
}/<code>

透明式合成模式

相比上面的安全式只有兩處改動:

  1. Composite :implements Conponent改為extends Conponent,其他地方無變化。
<code>publicclassCompositeextendsComponent{
...
}/<code>
  1. Leaf:此類將implements Conponent改為extends Conponent,其他地方無變化。
<code>publicclassLeafextendsComponent{
...
}/<code>

客戶端調用:

<code>publicclassClient{
publicstaticvoidmain(String[]args){
Componentroot=newComposite("服裝");
Componentc1=newComposite("男裝");
Componentc2=newComposite("女裝");

Componentleaf1=newLeaf("襯衫");
Componentleaf2=newLeaf("夾克");
Componentleaf3=newLeaf("裙子");
Componentleaf4=newLeaf("套裝");


root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);

root.printStruct("");
}
}/<code>

可以看出,客戶端無需再區分操作的是樹枝對象(Composite)還是樹葉對象(Leaf)了;對於客戶端而言,操作的都是Component對象。

使用場景

Java集合中的組合模式

HashMap 提供 putAll 的方法,可以將另一個 Map 對象放入自己的存儲空間中,如果有相同的 key 值則會覆蓋之前的 key 值所對應的 value 值

<code>publicclassTest{
publicstaticvoidmain(String[]args){
Map<string>map1=newHashMap<string>();
map1.put("aa",1);
map1.put("bb",2);
map1.put("cc",3);
System.out.println("map1:"+map1);

Map<string>map2=newLinkedMap();
map2.put("cc",4);
map2.put("dd",5);
System.out.println("map2:"+map2);

map1.putAll(map2);
System.out.println("map1.putAll(map2):"+map1);
}
}/<string>/<string>/<string>/<code>

更多應用場景可參考:

https://blog.csdn.net/wwwdc1012/article/details/82945703

  • java.awt中的組合模式
  • Mybatis SqlNode中的組合模式

優缺點

優點

  • 組合模式使得客戶端代碼可以一致地處理單個對象和組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了客戶端代碼
  • 更容易在組合體內加入新的對象,客戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”

缺點

  • 設計較複雜,客戶端需要花更多時間理清類之間的層次關係
  • 不容易限制容器中的構件
  • 不容易用繼承的方法來增加構件的新功能

兩種合成模式:安全性合成模式和透明性合成模式的優劣

  • 安全性合成模式是指:從客戶端使用合成模式上看是否更安全,如果是安全的,那麼就不會有發生誤操作的可能,能訪問的方法都是被支持的。
  • 透明性合成模式是指:從客戶端使用合成模式上,是否需要區分到底是“樹枝對象”還是“樹葉對象”。如果是透明的,那就不用區分,對於客戶而言,都是Compoent對象,具體的類型對於客戶端而言是透明的,是無須關心的。

對於合成模式而言,在安全性和透明性上,會更看重透明性,畢竟合成模式的目的是:讓客戶端不再區分操作的是樹枝對象還是樹葉對象,而是以一個統一的方式來操作。

而且對於安全性的實現,需要區分是樹枝對象還是樹葉對象。有時候,需要將對象進行類型轉換,卻發現類型信息丟失了,只好強行轉換,這種類型轉換必然是不夠安全的。

因此在使用合成模式的時候,建議多采用透明性的實現方式。 

參考

  • 《Java與模式》
  • https://www.cnblogs.com/lfxiao/p/6816026.html
  • https://www.jianshu.com/p/3a1885d26dff
  • https://www.cnblogs.com/betterboyz/p/9356458.html

關注我

我是一名後端開發工程師。

主要關注後端開發,數據安全,爬蟲,物聯網,邊緣計算等方向,歡迎交流。

各大平臺都可以找到我

  • 微信公眾號:後端技術漫談
  • Github:@qqxx6661
  • CSDN:@Rude3knife
  • 知乎:@後端技術漫談
  • 簡書:@後端技術漫談
  • 掘金:@蠻三刀把刀
  • Java面試知識點複習全手冊
  • 設計模式/數據結構
  • LeetCode/劍指offer 算法題解析
  • SpringBoot/SpringCloud菜鳥入門實戰系列
  • 爬蟲相關技術文章
  • 後端開發相關技術文章
  • 逸聞趣事/好書分享/個人生活
「設計模式」組合模式詳解

如果文章對你有幫助,不妨收藏,投幣,轉發,在看起來~


分享到:


相關文章: