組合模式(合成模式 COMPOSITE)
意圖
將對象組合成樹形結構以表示“部分-整體”的層次結構。
Composite使得用戶對單個對象和組合對象的使用具有一致性。
樹形結構介紹
為了便於理解,我們先介紹一下樹形結構
什么是樹形結構?
windows系統的文件夾樹形結構,部門組織架構,行政區...都是一種樹形結構
對於最終的節點,稱之為葉子;否則是樹枝
對於樹形結構經常會有一種使用場景:對他們下發一致性的指令
比如:對於操作系統有刪除操作,即可以刪除一個文件,也可以刪除一個文件夾,包括他下面所有的文件
類似刪除操作這種命令,並不關心這到底是一個文件(葉子)還是一個文件夾(樹枝),關心在意的只是要刪除目標
但是對於不同的類型,文件(葉子)還是 文件夾(樹枝),他們的處理又的確是不同的
文件只需要刪除就可以了,文件夾還需要遞歸遍歷內部的文件夾,直至所有的葉子節點,並且將他們全部刪除
這就出現了一個矛盾
客戶端其實並不關心到底是葉子還是樹枝
但是他卻不得不“關心”,因為他需要分情況處理
客戶端當然希望不關注目標的具體類型,也就是希望能夠:滿足依賴倒置原則,不關注具體類型,面向抽象進行編程
最簡單的方法就是將葉子和樹枝抽象出來一種新的類型組件
如此一來,刪除操作僅僅關心組件類型,不在關注到底是葉子還是樹枝
組件提供統一的協議約定,葉子和樹枝共同實現,將它們的不同點的細節封裝到他們內部的方法中
這就能夠讓用戶“單個對象和組合對象的使用具有一致性”。
所以,組合模式就是對於樹形結構場景下的一種使用模式
共同的抽象提取為新的組件Component,可以表示葉子或者樹枝
但是需要注意到:樹枝可以有多個樹葉組成,樹枝上面也可能是樹枝,也就是說,作為樹枝的節點,也會包含Component
所以完整的結構圖為:
所以組合模式的意圖,從結構圖中的Component中就可以看出來
他們都是Component,所以具有一致性。
借助於Composite與Component的關系,又能夠表述整體與部分的關系。
結構
Component 抽象構建角色
根據單個對象和組合對象的特點,規定的一個抽象角色(接口或抽象類),定義了共同的行為
或者說將"整體"和"部分"提取共性,進行抽象提取。
Leaf 葉子角色
參與組合對象的單個對象,也就是定義了參加組合對象的原始根本對象的行為
葉子節點下沒有下級對象
參與組合對象的單個對象,也就是定義了參加組合對象的原始根本對象的行為
葉子節點下沒有下級對象
Composite組合對象角色
也就是樹枝角色,單個對象組合起來的一個對象,由多個單一對象構成
並且給出組合對象的行為(實現Component約定的行為)
Client客戶端角色
給單個對象或者組合對象施加命令,也就是調用Component中的方法,比如刪除行為
示例代碼
以刪除文件為例
FileSystem文件系統類 擁有刪除方法delete()
他有兩個實現類文件 File 和文件夾Folder
Folder中可以有文件和文件夾,使用內部的List<FileSystem>保存
File的delete方法直接刪除,Folder則會便利內部的List<FileSystem> 逐個刪除
package composite; public interface FileSystem { void delete(); }
package composite; public class File implements FileSystem { @Override public void delete() { System.out.println("delete file..."); } }
package composite; import java.util.ArrayList; import java.util.List; public class Folder implements FileSystem { List<FileSystem> fileSystemList = new ArrayList<>(); @Override public void delete() { for(FileSystem fileSystem:fileSystemList){ fileSystem.delete(); } } public void add(FileSystem fileSystem){ fileSystemList.add(fileSystem); } }
客戶端
package composite; public class Client { public static void del(FileSystem fileSystem){ fileSystem.delete(); System.out.println("DELETED"); System.out.println(); } public static void main(String[] args){ Folder folder = new Folder(); Folder folder1 = new Folder(); Folder folder2 = new Folder(); Folder folder3 = new Folder(); File file1 = new File(); File file2 = new File(); File file3 = new File(); File file4 = new File(); folder.add(file1); folder.add(folder1); folder.add(folder2); folder1.add(file2); folder1.add(folder3); folder3.add(file4); folder2.add(file3); del(folder); del(folder1); del(file2); del(folder2); } }
客戶端Client中 del(FileSystem fileSystem) 方法用於對整個組件進行命令下達
內部調用組件FileSystem的delete方法
通過文件夾Folder的add方法我們構建了下面這種形式的樹形結構
通過下面代碼進行測試
del(folder);
del(folder1);
del(file2);
del(folder2);
在示例代碼中,借助於FileSystem這一抽象的組件Componet
將File 這一Leaf角色和 Folder 這一Composite角色 組織成 “部分--整體”的樹形結構
並且,對於客戶端提供統一的外在形式----Component
使得客戶端對單個對象和組合對象的使用具有一致性
這就是組合模式的運用
兩種形式
如果你有留意,可以看得到前面的代碼示例中
FileSystem中僅僅只有一個delete方法
File也實現了這一個方法,但是Folder 中卻有了add方法
也就是說,Composite角色中有與Component中不同的方法!
樹枝中可以有樹枝或者葉子節點,也就是組合對象中可以包含組合對象或者單一對象
那么,也就是說:組合對象要提供子對象的管理方法,比如上面的add 可能還會有remove等
上面的例子中,我們將add方法安置於Composite 中
這被稱為安全方式的合成模式
因為是在Composite中管理子對象,葉子節點類型的對象根本就沒有這些方法,所以也不能對客戶端執行這些方法
但是,葉子節點和樹枝節點不夠透明,他們擁有不同的方法
另外一種是將子對象的管理全部托管在Component中
也就是葉子節點和樹枝節點都將擁有這些方法,方法都是一樣的,對客戶端來說,葉子和樹枝在方法接口層面上的區別沒有了
客戶端可以完全同等的對待它們兩者,這就是透明方式的合成模式
但是,它不夠安全,因為葉子節點和樹枝節點邏輯上本來就是不相同的
葉子節點也不會有下一級子節點,所以這些方法沒有意義,而且如果使用編譯期間也不會報錯,會把問題留到運行中
兩種方式中,透明就不夠安全,安全就不透明,所以根據實際情況按照需求進行選擇
總結
組合模式的根本在於抽象組件,對於具有整體與部分關系的事物,如果需要一致性的外在表現,就可以提取共性進行抽象,這就是組合模式。
將相關聯的對象組織成“部分--整體”的樹形結構形式,通過抽象構建,所有的節點都是Component
對於客戶端來說,不管到底是Leaf還是Composite,他們都是Component
高層模塊並不需要關心,處理的到底是單個對象還是組合的對象
只要是比較符合“部分--整體”關系,或者說是樹形結構,以及當你希望用戶可以忽略組合對象和單個對象的區別時,那么就可以考慮使用組合模式
當增加新類型組件時,新定義的Composite或者Leaf子類自動的與已有的結構和客戶代碼一起工作
客戶端程序不需要因此而變化,從這個角度看,符合開閉原則。