訪問者模式的引入
想象一下铭污,您的團(tuán)隊(duì)開發(fā)了一個(gè)應(yīng)用程序葛作,該應(yīng)用程序?qū)⒌乩硇畔⒔Y(jié)構(gòu)化為一個(gè)巨大的圖形腕柜。圖中的每個(gè)節(jié)點(diǎn)可以表示一個(gè)復(fù)雜的實(shí)體济似,如城市,也可以表示更精細(xì)的事物盏缤,如工業(yè)砰蠢、觀光區(qū)等。如果它們所表示的真實(shí)對(duì)象之間存在道路唉铜,則節(jié)點(diǎn)與其他節(jié)點(diǎn)連接台舱。在引擎蓋下,每個(gè)節(jié)點(diǎn)類型都由自己的類表示潭流,而每個(gè)特定節(jié)點(diǎn)都是一個(gè)對(duì)象竞惋。
在某個(gè)時(shí)刻,您需要實(shí)現(xiàn)將圖形導(dǎo)出為XML格式的任務(wù)灰嫉。起初拆宛,這份工作似乎很簡(jiǎn)單。您計(jì)劃向每個(gè)節(jié)點(diǎn)類添加一個(gè)導(dǎo)出方法讼撒,然后利用遞歸遍歷圖的每個(gè)節(jié)點(diǎn)浑厚,執(zhí)行導(dǎo)出方法。解決方案簡(jiǎn)單而優(yōu)雅:由于多態(tài)性椿肩,您沒有將調(diào)用導(dǎo)出方法的代碼耦合到具體的節(jié)點(diǎn)類瞻颂。
不幸的是,系統(tǒng)架構(gòu)師拒絕允許您更改現(xiàn)有節(jié)點(diǎn)類郑象。他說(shuō)贡这,代碼已經(jīng)在生產(chǎn)中,他不想冒險(xiǎn)破壞它厂榛,因?yàn)槟母闹锌赡艽嬖阱e(cuò)誤盖矫。必須將XML導(dǎo)出方法添加到所有節(jié)點(diǎn)類中XML導(dǎo)出方法必須添加到所有節(jié)點(diǎn)類中,如果任何錯(cuò)誤隨更改而泄漏击奶,則會(huì)有破壞整個(gè)應(yīng)用程序的風(fēng)險(xiǎn)辈双。
此外,他質(zhì)疑在節(jié)點(diǎn)類中使用XML導(dǎo)出代碼是否合理柜砾。這些課程的主要工作是處理大地?cái)?shù)據(jù)湃望。XML導(dǎo)出行為在那里看起來(lái)很奇怪。
拒絕還有另一個(gè)原因。很可能在這個(gè)功能實(shí)現(xiàn)之后证芭,市場(chǎng)部的人會(huì)要求您提供導(dǎo)出為不同格式的功能瞳浦,或者請(qǐng)求其他一些奇怪的東西。這將迫使你再次改變那些珍貴而脆弱的職業(yè)废士。
解決方案
Visitor模式建議您將新行為放置到一個(gè)名為Visitor的單獨(dú)類中叫潦,而不是嘗試將其集成到現(xiàn)有類中。必須執(zhí)行該行為的原始對(duì)象現(xiàn)在作為參數(shù)傳遞給訪問者的一個(gè)方法官硝,為該方法提供對(duì)對(duì)象中包含的所有必要數(shù)據(jù)的訪問矗蕊。
現(xiàn)在,如果該行為可以在不同類的對(duì)象上執(zhí)行呢氢架?例如傻咖,在我們的XML導(dǎo)出示例中,不同節(jié)點(diǎn)類的實(shí)際實(shí)現(xiàn)可能會(huì)有點(diǎn)不同达箍。因此没龙,訪問者類可以不定義一個(gè),而是定義一組方法缎玫,每個(gè)方法可以采用不同類型的參數(shù)
但是硬纤,我們?nèi)绾螠?zhǔn)確地調(diào)用這些方法,尤其是在處理整個(gè)圖時(shí)赃磨?這些方法具有不同的簽名筝家,因此我們不能使用多態(tài)性。要選擇能夠處理給定對(duì)象的適當(dāng)訪問者方法邻辉,我們需要檢查其類溪王。
然而,Visitor模式解決了這個(gè)問題值骇。它使用了一種稱為Double Dispatch的技術(shù)莹菱,這有助于在對(duì)象上執(zhí)行正確的方法,而無(wú)需繁瑣的條件吱瘩。與其讓客戶端選擇要調(diào)用的方法的適當(dāng)版本道伟,不如將此選項(xiàng)委托給作為參數(shù)傳遞給訪問者的對(duì)象?因?yàn)閷?duì)象知道自己的類使碾,所以它們能夠在訪問者身上選擇合適的方法蜜徽,而不會(huì)那么尷尬。他們“接受”訪問者票摇,并告訴訪問者應(yīng)該執(zhí)行什么訪問方法拘鞋。
這樣我們添加更多的行為時(shí),無(wú)需再次修改代碼矢门。實(shí)現(xiàn)一個(gè)新的訪問者類即可盆色。
訪問者模式接口圖
代碼
// 圖形類定義了一個(gè)accept方法隔躲,參數(shù)為訪問者接口可以接受訪問者對(duì)象
interface Shape is
method move(x, y)
method draw()
method accept(v: Visitor)
// 每一個(gè)具體類都需要實(shí)現(xiàn)accept方法,調(diào)用自己的訪問者方法
class Dot implements Shape is
// ...
// 通過(guò)Visitor接口調(diào)用不同的方法
method accept(v: Visitor) is
v.visitDot(this)
class Circle implements Shape is
// ...
method accept(v: Visitor) is
v.visitCircle(this)
class Rectangle implements Shape is
// ...
method accept(v: Visitor) is
v.visitRectangle(this)
class CompoundShape implements Shape is
// ...
method accept(v: Visitor) is
v.visitCompoundShape(this)
// Visitor 接口實(shí)現(xiàn)了各種版本的相似方法案训,訪問者方法
interface Visitor is
method visitDot(d: Dot)
method visitCircle(c: Circle)
method visitRectangle(r: Rectangle)
method visitCompoundShape(cs: CompoundShape)
// 訪問者類的具體實(shí)現(xiàn)了各種版本的訪問者方法
class XMLExportVisitor implements Visitor is
method visitDot(d: Dot) is
// 導(dǎo)出 dot的 ID.
method visitCircle(c: Circle) is
// 導(dǎo)出circle的 ID
method visitRectangle(r: Rectangle) is
// 導(dǎo)出rectangle的 SID
method visitCompoundShape(cs: CompoundShape) is
// 導(dǎo)出compoundSchape的 ID
// 客戶端調(diào)用時(shí),可以遍歷復(fù)雜類轩触,直接調(diào)用訪問者方法脱柱,為無(wú)需關(guān)注類的類型煌茴,再去選擇對(duì)應(yīng)的方法矩乐。
class Application is
field allShapes: array of Shapes
method export() is
exportVisitor = new XMLExportVisitor()
foreach (shape in allShapes) do
shape.accept(exportVisitor)
優(yōu)缺點(diǎn)
1、優(yōu)點(diǎn)
- 符合開閉原則,因?yàn)榭梢詾椴煌念愐胄碌墓δ芑猓挥眯薷倪@些類。
- 單一職責(zé)原則,因?yàn)榭梢詫⒍鄠€(gè)版本的同一功能的方法集成到同一個(gè)類里面。
- 訪問者類可以在訪問不同的類時(shí)積累一些有用信息,這對(duì)于遍歷復(fù)雜的對(duì)象結(jié)構(gòu)月培,例如對(duì)象樹衷恭,時(shí)很方便匾荆。
2、缺點(diǎn)
- 每次在元素層次結(jié)構(gòu)中添加或刪除類時(shí)构罗,都需要更新所有訪問者類芙代,這點(diǎn)又違反了開閉原則。
- 訪問者類無(wú)法訪問他們?cè)L問的的元素的私有字段和方法盖彭。
- 解耦了數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)操作纹烹,使得操作集合可以獨(dú)立變化页滚。
應(yīng)用場(chǎng)景
- 數(shù)據(jù)結(jié)構(gòu)穩(wěn)定,但是數(shù)據(jù)結(jié)構(gòu)上的操作經(jīng)常變化铺呵,比如將數(shù)據(jù)導(dǎo)出為不同的格式裹驰。
- 需要對(duì)不同數(shù)據(jù)類型結(jié)構(gòu)體進(jìn)行操作,不同類型的操作類似但不相同片挂。
訪問者模式和其他模式的關(guān)系
- 可以將訪問者模式作為命令模式的加強(qiáng)版本幻林。它的對(duì)象可以在不同類的各種對(duì)象上執(zhí)行操作。
- 可以在組合模式組裝的數(shù)據(jù)樹上面使用訪問者模式音念。
- 也可以在復(fù)雜的數(shù)據(jù)結(jié)構(gòu)例如迭代器模式使用并對(duì)其元素執(zhí)行不同操作滋将,即使它們都具有不同的類。
個(gè)人理解
- 訪問者模式類似于類的方法重載症昏,不同的類調(diào)用同一個(gè)方法可以有不同的實(shí)現(xiàn)。
- 但是又將方法的具體實(shí)現(xiàn)獨(dú)立于類之外父丰,所以無(wú)法使用類的私有變量肝谭。
- 由于獨(dú)立可以跳出類本身的限制實(shí)現(xiàn)修改功能無(wú)需修改類本身的效果。
對(duì)靜態(tài)檢查應(yīng)用的思考
- 靜態(tài)多個(gè)工具的檢查實(shí)現(xiàn)是通過(guò)類的繼承方式從基類繼承實(shí)現(xiàn)的蛾扇,操作相似的類集成前一個(gè)類攘烛,兩個(gè)檢查項(xiàng)本身沒有相關(guān)性,所以相互繼承有點(diǎn)不好理解镀首。
- 考慮使用訪問者模式坟漱,將相同功能的不同實(shí)現(xiàn)抽象為單獨(dú)功能的訪問者,不同工具實(shí)現(xiàn)各自的調(diào)度邏輯更哄,功能函數(shù)指責(zé)單一芋齿。
- 由于訪問者模式的優(yōu)勢(shì)是對(duì)于復(fù)雜數(shù)據(jù)結(jié)構(gòu)類的處理上,對(duì)靜態(tài)檢查不同工具檢查使用訪問者模式不是特別合適成翩,并不會(huì)改變不同類的檢查邏輯相似觅捆,可以相互繼承的現(xiàn)狀。