本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/8135083.html,記錄一下學習過程以備後續查用。 一、引言 今天我們要講行為型設計模式的第九個模式--訪問者模式。如果按老規矩,先從名稱上來看這個模式,我根本不能獲得任何對理解該模式有用的信息, 而且這個 ...
本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/8135083.html,記錄一下學習過程以備後續查用。
一、引言
今天我們要講行為型設計模式的第九個模式--訪問者模式。如果按老規矩,先從名稱上來看這個模式,我根本不能獲得任何對理解該模式有用的信息,
而且這個模式在我們的編碼生活中使用的並不是很多。該模式的意圖定義很抽象,第一次看了這個定義其實和沒看沒有什麼區別,一頭霧水。為了讓大家
更好的理解該模式的初衷,我們舉個例子來說明模式。比如:當客戶提出一個新的軟體需求的時候,大家經過多個日以繼夜的努力,終於通過一個比較不
錯的軟體設計解決了客戶的需求,而且這個設計有相對完美的類層次結構並且符合OO的設計原則,大家為此感到開心,頗有成就感。又過了一段時間,
客戶突然又有一個新的需求,需要在現有的類層次結構裡面增加一個新的操作(其實就是一個方法),怎麼辦?還好,在面向OO設計模式中有一個模式
就是為瞭解決這個問題的,那就是訪問者模式,下麵讓我們好好地瞭解一下該模式。
二、訪問者模式介紹
訪問者模式:英文名稱--Visitor Pattern;分類--行為型。
2.1、動機(Motivate)
在軟體構建過程中,由於需求的改變,某些類層次結構中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改,將會給子類帶來很繁重的
變更負擔,甚至破壞原有設計。如何在不更改類層次結構的前提下,在運行時根據需要透明地為類層次結構上的各個類動態添加新的操作?
2.2、意圖(Intent)
表示一個作用於某對象結構中的各個元素的操作。它可以在不改變各元素的類的前提下定義作用於這些元素的新的操作。——《設計模式》GoF
2.3、結構圖(Structure)
2.4、模式的組成
可以看出,在訪問者模式的結構圖有以下角色:
1)抽象訪問者角色(Visitor): 聲明一個包括多個訪問操作,多個操作針對多個具體節點角色(可以說有多少個具體節點角色就有多少訪問操作),使
得所有具體訪問者必須實現的介面。
2)具體訪問者角色(ConcreteVisitor):實現抽象訪問者角色中所有聲明的介面,也可以說是實現對每個具體節點角色的新的操作。
3)抽象節點角色(Element):聲明一個接受操作,接受一個訪問者對象作為參數,如果有其它參數,可以在這個“接受操作”里在定義相關的參數。
4)具體節點角色(ConcreteElement):實現抽象元素所規定的接受操作。
5)結構對象角色(ObjectStructure):節點的容器,可以包含多個不同類或介面的容器。
2.5、訪問者模式的具體實現
訪問者模式在我們現實的編碼生活中使用的並不是很多,我就直接貼代碼,讓大家看看代碼的結構吧。今天給大家兩個代碼實例,實現代碼如下:

class Program { /// <summary> /// 抽象圖形定義--相當於“抽象節點角色” /// </summary> public abstract class Shape { //畫圖形 public abstract void Draw(); //外界註入具體訪問者 public abstract void Accept(ShapeVisitor visitor); } /// <summary> /// 矩形--相當於“具體節點角色” /// </summary> public sealed class Rectangle : Shape { public override void Draw() { Console.WriteLine("矩形我已經畫好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 圓形--相當於“具體節點角色” /// </summary> public sealed class Circle : Shape { public override void Draw() { Console.WriteLine("圓形我已經畫好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 直線--相當於“具體節點角色” /// </summary> public sealed class Line : Shape { public override void Draw() { Console.WriteLine("直線我已經畫好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 抽象訪問者 /// </summary> public abstract class ShapeVisitor { public abstract void Visit(Rectangle shape); public abstract void Visit(Circle shape); public abstract void Visit(Line shape); //這裡有一點要說:Visit方法的參數可以寫成Shape嗎?就是這樣Visit(Shape shape)。 //答案是可以,但是ShapeVisitor子類的Visit方法就需要判斷當前的Shape是什麼類型?是Rectangle類型?還是Circle類型?或者是Line類型? } /// <summary> /// 具體訪問者 /// </summary> public sealed class CustomVisitor : ShapeVisitor { //針對Rectangle對象 public override void Visit(Rectangle shape) { Console.WriteLine("針對Rectangle新的操作。"); } //針對Circle對象 public override void Visit(Circle shape) { Console.WriteLine("針對Circle新的操作。"); } //針對Line對象 public override void Visit(Line shape) { Console.WriteLine("針對Line新的操作。"); } } /// <summary> /// 結構對象角色 /// </summary> internal class AppStructure { private readonly ShapeVisitor _visitor; public AppStructure(ShapeVisitor visitor) { _visitor = visitor; } public void Process(Shape shape) { shape.Accept(_visitor); } } static void Main(string[] args) { #region 訪問者模式(第一個實例) ShapeVisitor visitor = new CustomVisitor(); AppStructure app = new AppStructure(visitor); Shape shape = new Rectangle(); shape.Draw(); //執行自己的操作 app.Process(shape); //執行新的操作 shape = new Circle(); shape.Draw(); //執行自己的操作 app.Process(shape); //執行新的操作 shape = new Line(); shape.Draw(); //執行自己的操作 app.Process(shape); //執行新的操作 Console.ReadLine(); #endregion } }View Code
運行結果如下:
這是訪問者模式第二個代碼實例:

class Program { /// <summary> /// 抽象訪問者角色 /// </summary> public abstract class Visitor { public abstract void PutTelevision(Television tv); public abstract void PutComputer(Computer comp); } /// <summary> /// 具體訪問者角色 /// </summary> public sealed class SizeVisitor : Visitor { public override void PutTelevision(Television tv) { Console.WriteLine("按電視大小{0}排放。", tv.Size); } public override void PutComputer(Computer comp) { Console.WriteLine("按電腦大小{0}排放。", comp.Size); } } /// <summary> /// 具體訪問者角色 /// </summary> public sealed class StateVisitor : Visitor { public override void PutTelevision(Television tv) { Console.WriteLine("按電視新舊值{0}排放", tv.State); } public override void PutComputer(Computer comp) { Console.WriteLine("按電腦新舊值{0}排放", comp.State); } } /// <summary> /// 抽象節點角色 /// </summary> public abstract class Goods { public abstract void Operate(Visitor visitor); public int Size { get; set; } public int State { get; set; } } /// <summary> /// 具體節點角色 /// </summary> public sealed class Television : Goods { public override void Operate(Visitor visitor) { visitor.PutTelevision(this); } } /// <summary> /// 具體節點角色 /// </summary> public sealed class Computer : Goods { public override void Operate(Visitor visitor) { visitor.PutComputer(this); } } /// <summary> /// 結構對象角色 /// </summary> public sealed class StoragePlatform { private IList<Goods> list = new List<Goods>(); public void Attach(Goods element) { list.Add(element); } public void Detach(Goods element) { list.Remove(element); } public void Operate(Visitor visitor) { foreach (Goods g in list) { g.Operate(visitor); } } } static void Main(string[] args) { #region 訪問者模式(第二個實例) StoragePlatform platform = new StoragePlatform(); platform.Attach(new Television()); platform.Attach(new Computer()); SizeVisitor sizeVisitor = new SizeVisitor(); StateVisitor stateVisitor = new StateVisitor(); platform.Operate(sizeVisitor); platform.Operate(stateVisitor); Console.Read(); #endregion } }View Code
運行結果如下:
三、訪問者模式的實現要點
Visitor模式通過所謂雙重分發(double dispatch)來實現在不更改Element類層次結構的前提下,在運行時透明地為類層次結構上的各個類動態添加新
的操作。所謂雙重分發即Visitor模式中間包括了兩個多態分發(註意其中的多態機制):第一個為accept方法的多態辨析;第二個為visit方法的多態辨析。
設計模式其實是一種堵漏洞的方式,但是沒有一種設計模式能夠堵完所有的漏洞,即使是組合各種設計模式也是一樣。每個設計模式都有漏洞,都有
它們解決不了的情況或者變化。每一種設計模式都假定了某種變化,也假定了某種不變化。Visitor模式假定的就是操作變化,而Element類層次結構穩定。
3.1、訪問者模式的主要優點
1)訪問者模式使得添加新的操作變得容易。如果一些操作依賴於一個複雜的結構對象的話,那麼一般而言,添加新的操作會變得很複雜。而使用訪問
者模式,增加新的操作就意味著添加一個新的訪問者類。因此,使得添加新的操作變得容易。
2)訪問者模式使得有關的行為操作集中到一個訪問者對象中,而不是分散到一個個的元素類中。這點類似與”中介者模式”。
3)訪問者模式可以訪問屬於不同的等級結構的成員對象,而迭代只能訪問屬於同一個等級結構的成員對象。
3.2、訪問者模式的主要缺點
1)增加新的元素類變得困難。每增加一個新的元素意味著要在抽象訪問者角色中增加一個新的抽象操作,併在每一個具體訪問者類中添加相應的具體
操作。具體來說,Visitor模式的最大缺點在於擴展類層次結構(增添新的Element子類),會導致Visitor類的改變。因此Visitor模式適用於“Element類層次
結構穩定,而其中的操作卻經常面臨頻繁改動”。
3.3、在下麵的情況下可以考慮使用訪問者模式
1)如果系統有比較穩定的數據結構,而又有易於變化的演算法時,此時可以考慮使用訪問者模式,因為訪問者模式使得演算法操作的添加比較容易。
2)如果一組類中,存在著相似的操作,為了避免出現大量重覆的代碼,可以考慮把重覆的操作封裝到訪問者中(當然也可以考慮使用抽象類了)。
3)如果一個對象存在著一些與本身對象不相干或關係比較弱的操作時,為了避免操作污染這個對象,則可以考慮把這些操作封裝到訪問者對象中。
四、.NET中訪問者模式的實現
在現在的.Net框架裡面,如果要想給現有的類增加新的方法,有了新的方式,那就是“擴展方法”,使用起來和實例方法是一樣一樣的,而且在.Net
框架裡面,微軟自己也寫了很多的擴展方法給我們使用。我目前還沒有學習到.Net的框架類庫裡面有“訪問者模式”實現,看來自己還需努力,革命尚
未成功啊。
五、總結
訪問者模式寫完了,這個模式剛開始理解起來還是挺麻煩的,但是,如果我們多看幾個實例代碼,完全掌握也不是問題。隨著C#語言的發展,設
計模式裡面的很多東西,我們可以通過C#語言的一些特性做更好的替代。我們寫設計模式剛開始要慢慢來,一步一步照貓畫虎地來寫代碼,等我們
熟練掌握了模式的核心思想,我們就要寫符合C#風格和特性的模式代碼了,或者說我們要用C#來寫設計模式了,寫出來的代碼也會更棒。