訪問者模式

訪問者模式的引入

想象一下铭污,您的團(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è)新的訪問者類即可盆色。

訪問者模式接口圖

image.png

代碼

// 圖形類定義了一個(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)狀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末麻敌,一起剝皮案震驚了整個(gè)濱河市栅炒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌术羔,老刑警劉巖赢赊,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異级历,居然都是意外死亡释移,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門寥殖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秀鞭,“玉大人趋观,你說(shuō)我怎么就攤上這事》姹撸” “怎么了皱坛?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)豆巨。 經(jīng)常有香客問我剩辟,道長(zhǎng),這世上最難降的妖魔是什么往扔? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任贩猎,我火速辦了婚禮,結(jié)果婚禮上萍膛,老公的妹妹穿的比我還像新娘吭服。我一直安慰自己,他們只是感情好蝗罗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布艇棕。 她就那樣靜靜地躺著,像睡著了一般串塑。 火紅的嫁衣襯著肌膚如雪沼琉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天桩匪,我揣著相機(jī)與錄音打瘪,去河邊找鬼。 笑死傻昙,一個(gè)胖子當(dāng)著我的面吹牛闺骚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妆档,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼葛碧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了过吻?” 一聲冷哼從身側(cè)響起进泼,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纤虽,沒想到半個(gè)月后乳绕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逼纸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年洋措,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杰刽。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菠发,死狀恐怖王滤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滓鸠,我是刑警寧澤雁乡,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站糜俗,受9級(jí)特大地震影響踱稍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悠抹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一珠月、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧楔敌,春花似錦啤挎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至氛谜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間区端,已是汗流浹背值漫。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留织盼,地道東北人杨何。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像沥邻,于是被迫代替她去往敵國(guó)和親危虱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容