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

































































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










































































































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
























































































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













































































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








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

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