天天看點

通路者模式

       考慮這樣一個應用:擴充客戶管理的功能。

       既然是擴充功能,那麼肯定是已經存在一定的功能了,先看看已有的功能:公司的客戶分成兩大類,一類是企業客戶,一類是個人客戶,現有的功能非常簡單,就是能讓客戶提出服務申請。目前的程式結構如圖所示:

通路者模式

現有的實作很簡單,先看看Customer的實作,示例代碼如下:

接下來看看企業客戶的實作示例代碼如下:

再看看個人客戶的實作示例代碼如下:

從上面的實作可以看出來,以前對客戶的管理功能是很少的,現在随着業務的發展,需要加強對客戶管理的功能,假設現在需要增加如下的功能:

客戶對公司産品的偏好分析,針對企業客戶和個人客戶有不同的分析政策,主要是根據以往購買的曆史、潛在購買意向等進行分析,對于企業客戶還要添加上客戶所在行業的發展趨勢、客戶的發展預期等的分析。

客戶價值分析,針對企業客戶和個人客戶,有不同的分析方式和政策。主要是根據購買的金額大小、購買的産品和服務的多少、購買的頻率等進行分析。

其實除了這些功能,還有很多潛在的功能,隻是現在還沒有要求實作,比如:針對不同的客戶進行需求調查;針對不同的客戶進行滿意度分析;客戶消費預期分析等等。雖然現在沒有要求實作,但不排除今後有可能會要求實作。

       要實作上面要求的功能,也不是很困難,一個很基本的想法就是:既然不同類型的客戶操作是不同的,那麼在不同類型的客戶裡面分别實作這些功能,不就可以了。

       由于這些功能的實作依附于很多其它功能的實作,或者是需要很多其它的業務資料,在示例裡面不太好完整的展現其功能實作,都是示意一下,是以提前說明一下。

通路者模式

按照這個思路,把程式示意實作出來,示例如下。

(1)先看看抽象的父類,主要就是加入了兩個新的方法,示例代碼如下:

(2)接下來看看企業客戶的示意實作,示例代碼如下:

(3)接下來看看個人客戶的示意實作,示例代碼如下:

(4)如何使用上面實作的功能呢,寫個用戶端來測試一下,示例代碼如下:

運作結果如下:

       以很簡單的方式,實作了要求的功能,這種實作有沒有什麼問題呢?仔細分析上面的實作,發現有兩個主要的問題:

在企業客戶和個人客戶的類裡面,都分别實作了提出服務請求、進行産品偏好分析、進行客戶價值分析等功能,也就是說,這些功能的實作代碼是混雜在同一個類裡面的;而且相同的功能分散到了不同的類中去實作,這會導緻整個系統難以了解、難以維護。

更為痛苦的是,采用這樣的實作方式,如果要給客戶擴充新的功能,比如前面提到的針對不同的客戶進行需求調查;針對不同的客戶進行滿意度分析;客戶消費預期分析等等。每次擴充,都需要改動企業客戶的類和個人客戶的類,當然也可以通過為它們擴充子類的方式,但是這樣可能會造成過多的對象層次。

那麼有沒有辦法,能夠在不改變客戶這個對象結構中各元素類的前提下,為這些類定義新的功能?也就是要求不改變企業客戶和個人客戶類,就能為企業客戶和個人客戶類定義新的功能?

用來解決上述問題的一個合理的解決方案,就是使用通路者模式。那麼什麼是通路者模式呢?

(1)通路者模式定義

通路者模式

(2)應用通路者模式來解決的思路

仔細分析上面的示例,對于客戶這個對象結構,不想改變類,又要添加新的功能,很明顯就需要一種動态的方式,在運作期間把功能動态地添加到對象結構中去。

有些朋友可能會想起裝飾模式,裝飾模式可以實作為一個對象透明的添加功能,但裝飾模式基本上是在現有的功能的基礎之上進行功能添加,實際上是對現有功能的加強或者改造。并不是在現有功能不改動的情況下,為對象添加新的功能。

       看來需要另外尋找新的解決方式了,可以應用通路者模式來解決這個問題,通路者模式實作的基本思路如下:

首先定義一個接口來代表要新加入的功能,為了通用,也就是定義一個通用的功能方法來代表新加入的功能

然後在對象結構上添加一個方法,作為通用的功能方法,也就是可以代表被添加的功能,在這個方法中傳入具體的實作新功能的對象

然後在對象結構的具體實作對象裡面實作這個方法,回調傳入具體的實作新功能的對象,就相當于調用到新功能上了

接下來的步驟就是提供實作新功能的對象

最後再提供一個能夠循環通路整個對象結構的類,讓這個類來提供符合用戶端業務需求的方法,來滿足用戶端調用的需要

這樣一來,隻要提供實作新功能的對象給對象結構,就可以為這些對象添加新的功能,由于在對象結構中定義的方法是通用的功能方法,是以什麼新功能都可以加入。

通路者模式的結構如圖所示:

通路者模式

Visitor

       通路者接口,為所有的通路者對象聲明一個visit方法,用來代表為對象結構添加的功能,理論上可以代表任意的功能。

ConcreteVisitor

       具體的通路者實作對象,實作要真正被添加到對象結構中的功能。

Element

       抽象的元素對象,對象結構的頂層接口,定義接受通路的操作。

ConcreteElement

       具體元素對象,對象結構中具體的對象,也是被通路的對象,通常會回調通路者的真實功能,同時開放自身的資料供通路者使用。

ObjectStructure

對象結構,通常包含多個被通路的對象,它可以周遊這多個被通路的對象,也可以讓通路者通路它的元素。可以是一個複合或是一個集合,如一個清單或無序集合。

但是請注意:這個ObjectStructure并不是我們在前面講到的對象結構,前面一直講的對象結構是指的一系列對象的定義結構,是概念上的東西;而ObjectStructure可以看成是對象結構中的一系列對象的一個集合,是用來輔助用戶端通路這一系列對象的,是以為了不造成大家的困惑,後面提到ObjectStructure的時候,就用英文名稱來代替,不把它翻譯成中文。

(1)首先需要定義一個接口來代表要新加入的功能,把它稱作通路者,通路誰呢?當然是通路對象結構中的對象了。既然是通路,不能空手而去吧,這些通路者在進行通路的時候,就會攜帶新的功能,也就是說,通路者攜帶着需要添加的新的功能去通路對象結構中的對象,就相當于給對象結構中的對象添加了新的功能。示例代碼如下:

(2)看看抽象的元素對象定義,示例代碼如下:

(3)接下來看看元素對象的具體實作,先看元素A的實作,示例代碼如下:

再看看元素B的實作,示例代碼如下:

(4)接下來看看通路者的具體實作,先看通路者1的實作,示例代碼如下:

通路者2的實作和通路者1的示意代碼是一樣的,就不去贅述了。

(5)該來看看ObjectStructure的實作了,示例代碼如下:

(6)接下來看看用戶端的示意實作,示例代碼如下:

       要使用通路者模式來重寫示例,首先就要按照通路者模式的結構,分離出兩個類層次來,一個是對應于元素的類層次,一個是對應于通路者的類層次。

對于對應于元素的類層次,現在已經有了,就是客戶的對象層次。而對應于通路者的類層次,現在還沒有,不過,按照通路者模式的結構,應該是先定義一個通路者接口,然後把每種業務實作成為一個單獨的通路者對象,也就是說應該使用一個通路者對象來實作對客戶的偏好分析,而用另外一個通路者對象來實作對客戶的價值分析。

       在分離好兩個類層次過後,為了友善用戶端的通路,定義一個ObjectStructure,其實就類似于前面示例中的客戶管理的業務對象。新的示例的結構如圖所示:

通路者模式

仔細檢視圖所示的程式結構示意圖,細心的朋友會發現,在圖上沒有出現對客戶進行價值分析的功能了。這是為了示範“使用通路者模式來實作示例功能過後,可以很容易的給對象結構增加新的功能”,是以先不做這個功能,等都實作好了,再來擴充這個功能。接下來還是看看代碼實作,以更好的體會通路者模式。

(1)先來看看Customer的代碼,Customer相當于通路者模式中的Element,它的實作跟以前相比有如下的改變:

新增一個接受通路者通路的方法

把能夠分離出去放到通路者中實作的方法,從Customer中删除掉,包括:客戶提出服務請求的方法、對客戶進行偏好分析的方法、對客戶進行價值分析的方法等

示例代碼如下:

(2)看看兩種客戶的實作,先看企業客戶的實作,示例代碼如下:

再看看個人客戶的實作,示例代碼如下:

(3)看看通路者的接口定義,示例代碼如下:

(4)接下來看看各個通路者的實作,每個通路者對象負責一類的功能處理,先看實作客戶提出服務請求的功能的通路者,示例代碼如下:

接下來看看實作對客戶偏好分析功能的通路者,示例代碼如下:

(5)接下來看看ObjectStructure的實作,示例代碼如下:

(6)該來寫個用戶端測試一下了,示例代碼如下:

(7)使用通路者模式來重新實作了前面示例的功能,把各類相同的功能放到單獨的通路者對象裡面,使得代碼不再雜亂,系統結構也更清晰,能友善的維護了,算是解決了前面示例的一個問題。

還有一個問題,就是看看能不能友善的增加新的功能,前面在示例的時候,故意留下了一個對客戶進行價值分析的功能沒有實作,那麼接下來就看看如何把這個功能增加到已有的系統中。在通路者模式中要給對象結構增加新的功能,隻需要把新的功能實作成為通路者,然後在用戶端調用的時候使用這個通路者對象來通路對象結構即可。

接下來看看實作對客戶價值分析功能的通路者,示例代碼如下:

使用這個功能,隻要在用戶端添加如下的代碼即可,示例代碼如下:

       通路者模式一個很常見的應用,就是群組合模式結合使用,通過通路者模式來給由組合模式建構的對象結構增加功能。

對于使用組合模式建構的組合對象結構,對外有一個統一的外觀,要想添加新的功能也不是很困難,隻要在元件的接口上定義新的功能就可以了,麻煩的是這樣一來,需要修改所有的子類。而且,每次添加一個新功能,都需要這麼痛苦一回,修改元件接口,然後修改所有的子類,這是相當糟糕的。

為了讓組合對象結構更靈活、更容易維護和更好的擴充性,接下來把它改造成通路者模式群組合模式組合來實作。這樣在今後再進行功能改造的時候,就不需要再改動這個組合對象結構了。

通路者模式群組合模式組合使用的思路:首先把組合對象結構中的功能方法分離出來,雖然維護組合對象結構的方法也可以分離出來,但是為了維持組合對象結構本身,這些方法還是放在組合對象結構裡面;然後把這些功能方法分别實作成為通路者對象,通過通路者模式添加到組合的對象結構中去。

下面通過通路者模式群組合模式組合來實作如下功能:輸出對象的名稱,在組合對象的名稱前面添加“節點:”,在葉子對象的名稱前面添加“葉子:”。

(1)先來定義通路者接口

通路者接口非常簡單,隻需要定義通路對象結構中不同對象的方法,示例代碼如下:

(2)改造組合對象的定義

       然後來對已有的組合對象進行改造,添加通用的功能方法,當然在參數上需要傳入通路者。先在元件定義上添加這個方法,然後到具體的實作類裡面去實作。除了新加這個方法外,元件定義沒有其它改變,示例代碼如下:

(3)實作組合對象和葉子對象

改變了元件定義,那麼需要在組合類和葉子類上分别實作這個方法,組合類中實作的時候,通常會循環讓所有的子元素都接受通路,這樣才能為所有的對象都添加上新的功能,示例代碼如下:

葉子對象的基本實作,示例代碼如下:

(4)實作一個通路者

       組合對象結構已經改造好了,現在需要提供一個通路者的實作,它會實作真正的功能,也就是要添加到對象結構中的功能。示例代碼如下:

(5)通路所有元素對象的對象——ObjectStructure

       通路者是給一系列對象添加功能的,是以一個通路者需要通路所有的對象,為了友善周遊整個對象結構,通常會定義一個專門的類出來,在這個類裡面進行元素疊代通路,同時這個類提供用戶端通路元素的接口。

       對于這個示例,由于在組合對象結構裡面,已經實作了對象結構的周遊,本來是可以不需要這個ObjectStructure的,但是為了更清晰的展示通路者模式的結構,也為了今後的擴充或實作友善,還是定義一個ObjectStructure。示例代碼如下:

(6)寫個用戶端,來看看如何通過通路者去為對象結構添加新的功能,示例代碼如下:

輸出的效果如下:

(7)現在的程式結構

       前面是分步的示範,大家已經體會了一番,接下來小結一下。

如同前面的示例,通路者的方法就相當于作用于組合對象結構中各個元素的操作,是一種通用的表達,同樣的通路者接口和同樣的方法,隻要提供不同的通路者具體實作,就表示不同的功能。

同時在組合對象中,接受通路的方法,也是一個通用的表達,不管你是什麼樣的功能,統統接受就好了,然後回調回去執行真正的功能。這樣一來,各元素的類就不用再修改了,隻要提供不同的通路者實作,然後通過這個通用表達,就結合到組合對象中來了,就相當于給所有的對象提供了新的功能。

示例的整體結構,如圖所示:

通路者模式

在通路者模式中,通路者必須要能夠通路到對象結構中的每個對象,因為通路者要為每個對象添加功能,為此特别在模式中定義出一個ObjectStructure來,然後由ObjectStructure來負責周遊通路一系列對象中的每個對象。

(1)在ObjectStructure疊代所有的元素時,又分成兩種情況。

一種是元素的對象結構是通過集合來組織的,那麼直接在ObjectStructure中對集合進行疊代,對每一個元素調用accept就好了。如同前面25.2.4的示例所采用的方式。

另一種情況是元素的對象結構是通過組合模式來組織的,通常可以構成對象樹,這種情況一般就不需要在ObjectStructure中疊代了,而通常的做法是在組合對象的accept方法裡面,遞歸周遊它的子元素,然後調用子元素的accept方法,如同前面25.3.2的示例中Composite的實作,在accept方法裡面進行遞歸調用子對象的操作。

(2)不需要ObjectStructure的時候

在實際開發中,有一種典型的情況可以不需要ObjectStructure對象,那就是隻有一個被通路對象的時候。隻有一個被通路對象,當然就不需要使用ObjectStructure來組合和疊代了,隻要調用這個對象就好了。

事實上還有一種情況也可以不使用ObjectStructure,比如上面通路的組合對象結構,從用戶端的角度看,他通路的其實就是一個對象,是以可以把ObjectStructure去掉,然後直接從用戶端調用元素的accept方法。

還是通過示例來說明,先把ObjectStructure類去掉,由于沒有了ObjectStructure,那麼用戶端調用的時候就直接調用組合對象結構的根元素的accept方法,示例代碼如下:

(3)有些時候,周遊元素的方法也可以放到通路者當中去,當然也是需要遞歸周遊它的子元素的。出現這種情況的主要原因是:想在通路者中實作特别複雜的周遊,通路者的實作依賴于對象結構的操作結果。

比如3.2的示例,使用通路者模式群組合模式組合來實作了輸出名稱的功能,如果現在要實作把組合的對象結構按照樹的形式輸出,就是按照在組合模式中示例的那樣,輸出如下的樹形結構:

要實作這個功能,在組合對象結構中去周遊子對象的方式就比較難于實作,因為要輸出這個樹形結構,需要控制每個對象在輸出的時候,向後的倒退數量,這個需要在對象結構的循環中來控制,這種功能可以選擇在通路者當中去周遊對象結構。

來改造上面的示例,看看通過通路者來周遊元素如何實作這樣的功能。

首先在Composite的accept實作中去除掉遞歸調用子對象的代碼,同時添加一個讓通路者通路到其所包含的子對象的方法,示例代碼如下:

然後新實作一個通路者對象,在相應的visit實作裡面,添加遞歸疊代所有子對象,示例代碼如下:

寫個用戶端來測試一下看看,是否能實作要求的功能。示例代碼如下:

 好的擴充性

    能夠在不修改對象結構中的元素的情況下,給對象結構中的元素添加新的功能

 好的複用性

    可以通過通路者來定義整個對象結構通用的功能,進而提高複用程度

 分離無關行為

    可以通過通路者來分離無關的行為,把相關的行為封裝在一起,構成一個通路者,這樣每一個通路者的功能都比較單一。

 對象結構變化很困難

    不适用于對象結構中的類經常變化的情況,因為對象結構發生了改變,通路者的接口和通路者的實作都要發生相應的改變,代價太高

 破壞封裝

    通路者模式通常需要對象結構開放内部資料給通路者和ObjectStructrue,這破壞了對象的封裝性

 通路者模式群組合模式

    這兩個模式可以組合使用。

    如同前面示例的那樣,通過通路者模式給組合對象預留下擴充功能的接口,使得給組合模式的對象結構添加功能非常容易。

 通路者模式和裝飾模式

    這兩個模式從表面看功能有些相似,都能夠實作在不修改原對象結構的情況下修改原對象的功能。但是裝飾模式更多的是實作對已有功能加強、或者修改、或者完全全新實作;而通路者模式更多的是實作給對象結構添加新的功能。

通路者模式和解釋器模式

解釋器模式在建構抽象文法樹的時候,是使用組合模式來建構的,也就是說解釋器模式解釋并執行的抽象文法樹是一個組合對象結構,這個組合對象結構是很少變動的,但是可能經常需要為解釋器增加新的功能,實作對同一對象結構的不同解釋和執行的功能,這正好是通路者模式的優勢所在,是以這在使用解釋器模式的時候通常會組合通路者模式來使用。

轉載至:http://sishuok.com/forum/blogPost/list/5881.html

   cc老師的設計模式是我目前看過最詳細最有實踐的教程。

繼續閱讀