類層次結(jié)構(gòu)的變化
類層次結(jié)構(gòu)中可能經(jīng)常由于引入新的操作,從而將類型變得脆弱……
動機(Motivation)
在軟件構(gòu)建過程中,由于需求的改變,某些類層次結(jié)構(gòu)中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改,將會給子類帶來很繁重的變更負(fù)擔(dān),甚至破壞原有設(shè)計。如何在不更改類層次結(jié)構(gòu)的前提下,在運行時根據(jù)需要透明地為類層次結(jié)構(gòu)上的各個類動態(tài)添加新的操作,從而避免上述問題?
意圖(Intent)
表示一個作用于某對象結(jié)構(gòu)中的各個元素的操作。它可以在不改變各元素的類的前提下定義作用于這些元素的新的操作。
——《設(shè)計模式》GoF
例說Visitor模式應(yīng)用
假設(shè)現(xiàn)在Shape需要增加一個新的MoveTo操作,它所有的子類都需要添加一個MoveTo的Override方法。
改進(jìn)的代碼
雖然子類的重載方法都是一樣的代碼,但是不能放在基類中寫成virtual方法。因為首先,Visit方法接收的類型不是多態(tài)類型,而是具體類型。傳入的this是一種編譯時綁定,它是一種靜態(tài)多態(tài)。如果是在基類中寫Accept虛方法,里面寫上v.Visit(this),編譯的時候會報錯,因為Visit方法里接收的類型沒有Shape類型,因此我們只能寫在子類中重寫。
現(xiàn)在,給各個子類增加操作,實際上就是給ShapeVisitor增加一個新的子類。
當(dāng)然,操作可能要傳入?yún)?shù),我們可以為Accept方法添加一個上下文信息
客戶程序
我們首先要new一個ShapeVisitor實例MyVisitor,shape.Accept方法先會執(zhí)行多態(tài)辨析,決定執(zhí)行Line的Accept方法,然后會執(zhí)行v.Visit方法,然后找到MyVisitor的Visit方法來執(zhí)行。整個過程有個雙重辨析,先調(diào)用了Accept,此步驟有一個虛函數(shù)的動態(tài)辨析;然后調(diào)用了Visit,此步驟也是一個虛函數(shù)的動態(tài)辨析。
這里有一個問題,我們可以不可以把Visit的參數(shù)直接寫成抽象類Shape呢?
實際上我們也可以這樣寫,這樣寫了之后,我們只需要在Shape基類中寫上Accept虛方法調(diào)用v.Visit(this),即可不需再在子類中重寫Accept方法了。但是這樣寫的問題是,我們在寫ShapeVisitor具體類的Visit方法時,就需要判斷Shape的類型,來做不同的處理。
這樣實際上是換湯不換藥,因為本來以前我們的辨析是重載辨析,靠編譯器來解決,現(xiàn)在需要靠我們自己寫if else來解決辨析。這兩種方法都同樣要面臨MyVisitor子類增加的問題。Shape如果增加子類,這種方式也需要增加一個if else分支。
結(jié)構(gòu)(Structure)
當(dāng)需要添加新的操作的時候,只需要添加一個ConcreteVisitor類即可。這種結(jié)構(gòu)把擴(kuò)展操作所帶來的改變轉(zhuǎn)嫁給了Visitor的子類。
Visitor模式的幾個要點
Visitor模式通過所謂雙重分發(fā)(double dispatch)來實現(xiàn)在不更改Element類層次結(jié)構(gòu)的前提下,在運行時透明地為類層次結(jié)構(gòu)上的各個類動態(tài)添加新的操作。所謂雙重分發(fā)即Visitor模式中間包括了兩個多態(tài)分發(fā)(注意其中的多態(tài)機制):第一個為accept方法的多態(tài)辨析;第二個為visit方法的多態(tài)辨析。
Visitor模式的最大缺點在于擴(kuò)展類層次結(jié)構(gòu)(增添新的Element子類),會導(dǎo)致Visitor類的改變。因此Visitor模式適用于“Element類層次結(jié)構(gòu)穩(wěn)定,而其中的操作卻經(jīng)常面臨頻繁改動”。
設(shè)計模式其實是一種堵漏洞的方式,但是沒有一種設(shè)計模式能夠堵完所有的漏洞,即使是組合各種設(shè)計模式也是一樣。每個設(shè)計模式都有漏洞,都有它們解決不了的情況或者變化。每一種設(shè)計模式都假定了某種變化,也假定了某種不變化。Visitor模式假定的就是操作變化,而Element類層次結(jié)構(gòu)穩(wěn)定。
it知識庫:C#面向?qū)ο笤O(shè)計模式縱橫談:Visitor 訪問者模式,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。