——.NET設計模式系列之十一
Terrylee
,
2006
年
3
月
概述
組合模式有時候又叫做部分
-
整體模式,它使我們樹型結構的問題中,模糊了簡單元素和復雜元素的概念,客戶程序可以向處理簡單元素一樣來處理復雜元素,從而使得客戶程序與復雜元素的內部結構解耦。
意圖
將對象組合成樹形結構以表示“部分
-
整體”的層次結構。
Composite
模式使得用戶對單個對象和組合對象的使用具有一致性。
[GOF
《設計模式》
]
結構圖
圖
1 Composite
模式結構圖
生活中的例子
組合模式將對象組合成樹形結構以表示
"
部分
-
整體
"
的層次結構。讓用戶一致地使用單個對象和組合對象。雖然例子抽象一些,但是算術表達式確實是組合的例子。算術表達式包括操作數、操作符和另一個操作數。操作數可以是數字,也可以是另一個表達式。這樣,
2+3
和(
2+3
)
+
(
4*6
)都是合法的表達式。
圖
2
使用算術表達式例子的
Composite
模式對象圖
組合模式解說
這里我們用繪圖這個例子來說明
Composite
模式,通過一些基本圖像元素(直線、圓等)以及一些復合圖像元素(由基本圖像元素組合而成)構建復雜的圖形樹。在設計中我們對每一個對象都配備一個
Draw()
方法,在調用時,會顯示相關的圖形。可以看到,這里復合圖像元素它在充當對象的同時,又是那些基本圖像元素的一個容器。先看一下基本的類結構圖:
圖
3
圖中橙色的區域表示的是復合圖像元素。示意性代碼:
而其他作為樹枝構件,實現代碼如下: 現在我們要對該圖像元素進行處理:在客戶端程序中,需要判斷返回對象的具體類型到底是基本圖像元素,還是復合圖像元素。如果是復合圖像元素,我們將要用遞歸去處理,然而這種處理的結果卻增加了客戶端程序與復雜圖像元素內部結構之間的依賴,那么我們如何去解耦這種關系呢?我們希望的是客戶程序可以像處理基本圖像元素一樣來處理復合圖像元素,這就要引入 Composite 模式了,需要把對于子對象的管理工作交給復合圖像元素,為了進行子對象的管理,它必須提供必要的 Add() , Remove() 等方法,類結構圖如下:
而其他作為樹枝構件,實現代碼如下: 現在我們要對該圖像元素進行處理:在客戶端程序中,需要判斷返回對象的具體類型到底是基本圖像元素,還是復合圖像元素。如果是復合圖像元素,我們將要用遞歸去處理,然而這種處理的結果卻增加了客戶端程序與復雜圖像元素內部結構之間的依賴,那么我們如何去解耦這種關系呢?我們希望的是客戶程序可以像處理基本圖像元素一樣來處理復合圖像元素,這就要引入 Composite 模式了,需要把對于子對象的管理工作交給復合圖像元素,為了進行子對象的管理,它必須提供必要的 Add() , Remove() 等方法,類結構圖如下:

































































圖4
示意性代碼:
這樣引入 Composite 模式后,客戶端程序不再依賴于復合圖像元素的內部實現了。然而,我們程序中仍然存在著問題,因為 Line , Rectangle , Circle 已經沒有了子對象,它是一個基本圖像元素,因此 Add() , Remove() 的方法對于它來說沒有任何意義,而且把這種錯誤不會在編譯的時候報錯,把錯誤放在了運行期,我們希望能夠捕獲到這類錯誤,并加以處理,稍微改進一下我們的程序: 這樣改進以后,我們可以捕獲可能出現的錯誤,做進一步的處理。上面的這種實現方法屬于透明式的 Composite 模式,如果我們想要更安全的一種做法,就需要把管理子對象的方法聲明在樹枝構件 Picture 類里面,這樣如果葉子節點 Line , Rectangle , Circle 使用這些方法時,在編譯期就會出錯,看一下類結構圖:
這樣引入 Composite 模式后,客戶端程序不再依賴于復合圖像元素的內部實現了。然而,我們程序中仍然存在著問題,因為 Line , Rectangle , Circle 已經沒有了子對象,它是一個基本圖像元素,因此 Add() , Remove() 的方法對于它來說沒有任何意義,而且把這種錯誤不會在編譯的時候報錯,把錯誤放在了運行期,我們希望能夠捕獲到這類錯誤,并加以處理,稍微改進一下我們的程序: 這樣改進以后,我們可以捕獲可能出現的錯誤,做進一步的處理。上面的這種實現方法屬于透明式的 Composite 模式,如果我們想要更安全的一種做法,就需要把管理子對象的方法聲明在樹枝構件 Picture 類里面,這樣如果葉子節點 Line , Rectangle , Circle 使用這些方法時,在編譯期就會出錯,看一下類結構圖:










































































































圖5
示意性代碼:
這種方式屬于安全式的 Composite 模式,在這種方式下,雖然避免了前面所討論的錯誤,但是它也使得葉子節點和樹枝構件具有不一樣的接口。這種方式和透明式的 Composite 各有優劣,具體使用哪一個,需要根據問題的實際情況而定。通過 Composite 模式,客戶程序在調用 Draw() 的時候不用再去判斷復雜圖像元素中的子對象到底是基本圖像元素,還是復雜圖像元素,看一下簡單的客戶端調用: .NET 中的組合模式
這種方式屬于安全式的 Composite 模式,在這種方式下,雖然避免了前面所討論的錯誤,但是它也使得葉子節點和樹枝構件具有不一樣的接口。這種方式和透明式的 Composite 各有優劣,具體使用哪一個,需要根據問題的實際情況而定。通過 Composite 模式,客戶程序在調用 Draw() 的時候不用再去判斷復雜圖像元素中的子對象到底是基本圖像元素,還是復雜圖像元素,看一下簡單的客戶端調用: .NET 中的組合模式
























































































如果有人用過
Enterprise Library2.0
,一定在源程序中看到了一個叫做
ObjectBuilder
的程序集,顧名思義,它是用來負責對象的創建工作的,而在
ObjectBuilder
中,有一個被稱為定位器的東西,通過定位器,可以很容易的找到對象,
它的結構采用鏈表結構,每一個節點是一個鍵值對,用來標識對象的唯一性,使得對象不會被重復創建。定位器的鏈表結構采用可枚舉的接口類來實現,這樣我們可以通過一個迭代器來遍歷這個鏈表。同時多個定位器也被串成一個鏈表。具體地說就是多個定位器組成一個鏈表,表中的每一個節點是一個定位器,定位器本身又是一個鏈表,表中保存著多個由鍵值對組成的對象的節點。所以這是一個典型的Composite模式的例子,來看它的結構圖:
正如我們在圖中所看到的,
IReadableLocator
定義了最上層的定位器接口方法,它基本上具備了定位器的大部分功能。
部分代碼:
一個抽象基類 ReadableLocator 用來實現這個接口的公共方法。兩個主要的方法實現代碼如下: 可以看到,在FindBy方法里面,循環調用了 FindInLocator 方法, 如果查詢選項是只查找當前定位器,那么循環終止,否則沿著定位器的父定位器繼續向上查找。FindInLocator方法就是遍歷定位器,然后把找到的對象存入一個臨時的定位器。最后返回一個只讀定位器的新的實例。
一個抽象基類 ReadableLocator 用來實現這個接口的公共方法。兩個主要的方法實現代碼如下: 可以看到,在FindBy方法里面,循環調用了 FindInLocator 方法, 如果查詢選項是只查找當前定位器,那么循環終止,否則沿著定位器的父定位器繼續向上查找。FindInLocator方法就是遍歷定位器,然后把找到的對象存入一個臨時的定位器。最后返回一個只讀定位器的新的實例。













































































從這個抽象基類中派生出一個具體類和一個抽象類,一個具體類是只讀定位器(
ReadOnlyLocator
),只讀定位器實現抽象基類沒有實現的方法,它封裝了一個實現了
IReadableLocator
接口的定位器,然后屏蔽內部定位器的寫入接口方法。另一個繼承的是讀寫定位器抽象類ReadWriteLocator,為了實現對定位器的寫入和刪除,這里定義了一個對
IReadableLocator
接口擴展的接口叫做
IReadWriteLocator
,在這個接口里面提供了實現定位器的操作:
實現代碼如下:








從ReadWirteLocator派生的具體類是Locator類,Locator類必須實現一個定位器的全部功能,現在我們所看到的Locator它已經具有了管理定位器的功能,同時他還應該具有存儲的結構,這個結構是通過一個WeakRefDictionary類來實現的,這里就不介紹了。[關于定位器的介紹參考了
niwalker
的Blog]
效果及實現要點
1
.
Composite
模式采用樹形結構來實現普遍存在的對象容器,從而將“一對多”的關系轉化“一對一”的關系,使得客戶代碼可以一致地處理對象和對象容器,無需關心處理的是單個的對象,還是組合的對象容器。
2
.將“客戶代碼與復雜的對象容器結構”解耦是
Composite
模式的核心思想,解耦之后,客戶代碼將與純粹的抽象接口——而非對象容器的復內部實現結構——發生依賴關系,從而更能“應對變化”。
3
.
Composite
模式中,是將“
Add
和
Remove
等和對象容器相關的方法”定義在“表示抽象對象的
Component
類”中,還是將其定義在“表示對象容器的
Composite
類”中,是一個關乎“透明性”和“安全性”的兩難問題,需要仔細權衡。這里有可能違背面向對象的“單一職責原則”,但是對于這種特殊結構,這又是必須付出的代價。
ASP.NET
控件的實現在這方面為我們提供了一個很好的示范。
4
.
Composite
模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。
適用性
以下情況下適用
Composite
模式:
1
.你想表示對象的部分
-
整體層次結構
2
.你希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象。
總結
組合模式解耦了客戶程序與復雜元素內部結構,從而使客戶程序可以向處理簡單元素一樣來處理復雜元素。
參考資料
閻宏,《
Java
與模式》,電子工業出版社
James W. Cooper
,《
C#
設計模式》,電子工業出版社
Alan Shalloway James R. Trott
,《
Design Patterns Explained
》,中國電力出版社
MSDN WebCast
《
C#
面向對象設計模式縱橫談
(9)
:
Composite
組合模式
(
結構型模式
)
》
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
