Visitor模式是一個用起來很簡單,理解起來可能稍微有一點困難的模式。不過明白了之後就清楚了,其實也是非常的簡單。
問題
需要向對象結構中增加新的方法,但是增加起來會很費勁或者會破壞設計。
案例
舉一個例子。假設我們有一組機器(Windows,Unix,Linux,Mac等),每種機器都有自己不同的底層結構。現在需要在各臺機器上裝一個軟件或者進行一項配置。由於每種操作系統都是不一樣的架構,所以做某種配置或者是安裝某款軟件的手順都是不同的,而且我們不可能也不想在原有的操作系統中直接添加一個同名的方法來實現這個功能。那麼這個時候,Visitor模式就是一個很好的利器了。
還有,最近做的一個工具也是比較適合使用Visitor模式。這個工具是類似於Findbugs的一種功能,要解析一種編程語言的源碼文件,然後對其中的內容進行分析,看是否符合某種類型的bug pattern。源碼文件首先將被解析成一個數據結構,然後每條規則只需要檢查這個數據結構中的一部分,並不需要檢查全部的數據結構。對於這種工具,Visitor模式算是再好不過的一種解決方案了。
圖示
下面兩幅類圖就是Visitor模式的一種普通的表現形式,它們表示的意思基本上是一致的。
Picture1
Picture2
Picture2和Picture1的區別只是visitor方法的名字不同而已。Picture1中的命名方法可能更常用一些,Picture2中的命名方法則更清晰一些。
以上面那兩幅類圖中,有一點可能稍微不容易理解,就是一定要在對象結構中的每一個類中增加一個accept(visitor),而且方法體裏面又只有一行代碼,就只是反調Visitor類。如果這樣的話,讓Visitor直接訪問數據結構不就行了嗎?
這個問題也讓我困擾了一陣子,後來纔有點明白,關鍵點就是使用了雙重分派。
Double Dispatch(雙重分派,Dual Dispatch)
面向對象的程序中有Overloading(重載)和Overriding(重寫)兩種概念。
Overriding是指子類重寫父類中已有的方法,那麼如果一個對象,雖然被申明爲父類類型,但是賦值是子類類型,那麼在對該對象調用方法時,程序將自動的調用子類中的方法,而回避父類中已有的方法。 這一點是在運行時才能夠判斷的。這個是動態分派。
Overloading是指一個類中,多個同名方法,但是參數不同,有的是父類對象,有的是子類對象。那麼在調用這個同名方法時,JVM會自動地根據傳入的參數的實際類型而調用不同的方法。 這一點是在編譯時就已經決定了的。這個就是靜態分派。
而Java和C++是動態單分派,靜態多分派語言。
以Picture1 爲例,Client中的代碼中,elemA.accept(visitor);在判斷到底是調用Element還是ConcreateElementA的accept方法時使用的是動態分派;而ConcreateElementA的accept方法中的代碼 visitor.visit(this); 在判斷到底是調用Visitor類的哪一個visit方法時,是根據this所指代的類型來決定的(也就是參數),這裏使用的是靜態分派。
回答之前那個疑問。如果不使用雙重分派,不在對象結構的類中增加accept方法的話,那麼對象結構中類之間的關係也就沒有意義了。Visitor.visit(elem)這樣的方法要能夠正確使用的話,就必須把elem聲明爲具體的類型,如ConcreateElementA,否則程序將直接嘗試調用Visitor.visit(Element)方法,而不是Visitor.visit(ConreateElementA)。
簡單一句話總結,調用方法的主體對象是動態分派的,而調用的方法是先前靜態分派好的。
適用對象結構
其實 visitor模式比較適用的對象結構是,其中的數據類型分屬於不同的類型。如果都已經是同一類型的話,那麼直接在對象結構的頂層類中做一些修改,可能就可以達到目的,多見於Composite模式。當然,如果是維護項目的話,不想或者不能修改原有數據結構也是常有的事情,這個時候使用visitor模式也是沒有問題的。
依據目的的不同,也可以使用Decorator模式來達成目的。
適用情況
1. 需要爲一個對象結構添加很多不同的操作
2. 對象結構一般是保持不變的,而操作是經常變化的
3. 操作是針對於對象結構中的具體類的,而不是僅僅針對於高層類
4. 對象結構的內部狀態和操作是可見的,或者是原來不可見但是改爲可見之後也不會有問題
參考資料
1. http://hi.baidu.com/xinsikao/blog/item/6ad8cbc4ac6189c038db4919.html
2. http://www.cnblogs.com/zhenyulu/articles/81761.html
3. http://www.cnblogs.com/perhaps/archive/2005/08/19/218466.html
4. http://www.cnblogs.com/idior/archive/2005/08/18/217500.html
http://en.wikipedia.org/wiki/Visitor_pattern
http://www.cnblogs.com/idior/archive/2005/01/19/94280.html
Double Dispatch:
http://www.blogjava.net/dreamstone/archive/2006/12/20/88947.html
http://www.eqqon.com/index.php/Double_Dispatch_Mechanism
http://en.wikipedia.org/wiki/Double_dispatch
http://ifacethoughts.net/2006/07/29/single-double-and-multiple-dispatch/