1、引言
在這篇博文中,我將爲你們分享我對訪問者模式的理解。html
2、訪問者模式介紹算法
2.1 訪問者模式的定義設計模式
訪問者模式是封裝一些施加於某種數據結構之上的操做。一旦這些操做須要修改的話,接受這個操做的數據結構則能夠保存不變。訪問者模式適用於數據結構相對穩定的系統, 它把數據結構和做用於數據結構之上的操做之間的耦合度下降,使得操做集合能夠相對自由地改變。
數據結構的每個節點均可以接受一個訪問者的調用,此節點向訪問者對象傳入節點對象,而訪問者對象則反過來執行節點對象的操做。這樣的過程叫作「雙重分派」。節點調用訪問者,將它本身傳入,訪問者則將某算法針對此節點執行。數據結構
2.2 訪問者模式的結構圖dom
從上面描述可知,訪問者模式是用來封裝某種數據結構中的方法。具體封裝過程是:每一個元素接受一個訪問者的調用,每一個元素的Accept方法接受訪問者對象做爲參數傳入,訪問者對象則反過來調用元素對象的操做。具體的訪問者模式結構圖以下所示:ide
這裏須要明確一點:訪問者模式中具體訪問者的數目和具體節點的數目沒有任何關係。從訪問者的結構圖能夠看出,訪問者模式涉及如下幾類角色。this
1)抽象訪問者角色(Vistor):聲明一個或多個訪問操做,使得全部具體訪問者必須實現的接口。spa
2)具體訪問者角色(ConcreteVistor):實現抽象訪問者角色中全部聲明的接口。設計
3)抽象節點角色(Element):聲明一個接受操做,接受一個訪問者對象做爲參數。code
4)具體節點角色(ConcreteElement):實現抽象元素所規定的接受操做。
5)結構對象角色(ObjectStructure):節點的容器,能夠包含多個不一樣類或接口的容器。
2.3 訪問者模式的實現
在講訴訪問者模式的實現時,我想先不用訪問者模式的方式來實現某個場景。具體場景是——如今我想遍歷每一個元素對象,而後調用每一個元素對象的Print方法來打印該元素對象的信息。若是此時不採用訪問者模式的話,實現這個場景再簡單不過了,具體實現代碼以下所示:
namespace DonotUsevistorPattern { // 抽象元素角色 public abstract class Element { public abstract void Print(); } // 具體元素A public class ElementA : Element { public override void Print() { Console.WriteLine("我是元素A"); } } // 具體元素B public class ElementB : Element { public override void Print() { Console.WriteLine("我是元素B"); } } // 對象結構 public class ObjectStructure { private ArrayList elements = new ArrayList(); public ArrayList Elements { get { return elements; } } public ObjectStructure() { Random ran = new Random(); for (int i = 0; i < 6; i++) { int ranNum = ran.Next(10); if (ranNum > 5) { elements.Add(new ElementA()); } else { elements.Add(new ElementB()); } } } } class Program { static void Main(string[] args) { ObjectStructure objectStructure = new ObjectStructure(); // 遍歷對象結構中的對象集合,訪問每一個元素的Print方法打印元素信息 foreach (Element e in objectStructure.Elements) { e.Print(); } Console.Read(); } } }
上面代碼很準確的解決了咱們剛纔提出的場景,可是需求在時刻變化的。若是此時,我除了想打印元素的信息外,還想打印出元素被訪問的時間。此時,咱們就不得不去修改每一個元素的Print方法,再加入相對應的輸入訪問時間的輸出信息。這樣的設計顯然不符合「開-閉」原則,即某個方法操做的改變,會使得必須去更改每一個元素類。既然,這裏變化的點是操做的改變,而每一個元素的數據結構是不變的。因此此時就思考——能不能把操做於元素的操做和元素自己的數據結構分開呢?解開這二者的耦合度,這樣若是是操做發現變化時,就不須要去更改元素自己了。可是,若是是元素數據結構發現變化,例如,添加了某個字段,這樣就不得不去修改元素類了。此時,咱們可使用訪問者模式來解決這個問題,即把做用於具體元素的操做由訪問者對象來調用。具體的實現代碼以下所示:
namespace VistorPattern { // 抽象元素角色 public abstract class Element { public abstract void Accept(IVistor vistor); public abstract void Print(); } // 具體元素A public class ElementA :Element { public override void Accept(IVistor vistor) { // 調用訪問者visit方法 vistor.Visit(this); } public override void Print() { Console.WriteLine("我是元素A"); } } // 具體元素B public class ElementB :Element { public override void Accept(IVistor vistor) { vistor.Visit(this); } public override void Print() { Console.WriteLine("我是元素B"); } } // 抽象訪問者 public interface IVistor { void Visit(ElementA a); void Visit(ElementB b); } // 具體訪問者 public class ConcreteVistor :IVistor { // visit方法而是再去調用元素的Accept方法 public void Visit(ElementA a) { a.Print(); } public void Visit(ElementB b) { b.Print(); } } // 對象結構 public class ObjectStructure { private ArrayList elements = new ArrayList(); public ArrayList Elements { get { return elements; } } public ObjectStructure() { Random ran = new Random(); for (int i = 0; i < 6; i++) { int ranNum = ran.Next(10); if (ranNum > 5) { elements.Add(new ElementA()); } else { elements.Add(new ElementB()); } } } } class Program { static void Main(string[] args) { ObjectStructure objectStructure = new ObjectStructure(); foreach (Element e in objectStructure.Elements) { // 每一個元素接受訪問者訪問 e.Accept(new ConcreteVistor()); } Console.Read(); } } }
從上面代碼可知,使用訪問者模式實現上面場景後,元素Print方法的訪問封裝到了訪問者對象中了(我以爲能夠把Print方法封裝到具體訪問者對象中。),此時客戶端與元素的Print方法就隔離開了。此時,若是須要添加打印訪問時間的需求時,此時只須要再添加一個具體的訪問者類便可。此時就不須要去修改元素中的Print()方法了。
3、訪問者模式的應用場景
每一個設計模式都有其應當使用的狀況,那讓咱們看看訪問者模式具體應用場景。若是遇到如下場景,此時咱們能夠考慮使用訪問者模式。
1)若是系統有比較穩定的數據結構,而又有易於變化的算法時,此時能夠考慮使用訪問者模式。由於訪問者模式使得算法操做的添加比較容易。
2)若是一組類中,存在着類似的操做,爲了不出現大量重複的代碼,能夠考慮把重複的操做封裝到訪問者中。(固然也能夠考慮使用抽象類了)
3)若是一個對象存在着一些與自己對象不相干,或關係比較弱的操做時,爲了不操做污染這個對象,則能夠考慮把這些操做封裝到訪問者對象中。
4、訪問者模式的優缺點
訪問者模式具備如下優勢:
1)訪問者模式使得添加新的操做變得容易。若是一些操做依賴於一個複雜的結構對象的話,那麼通常而言,添加新的操做會變得很複雜。而使用訪問者模式,增長新的操做就意味着添加一個新的訪問者類。所以,使得添加新的操做變得容易。
2)訪問者模式使得有關的行爲操做集中到一個訪問者對象中,而不是分散到一個個的元素類中。這點相似與"中介者模式"。
3)訪問者模式能夠訪問屬於不一樣的等級結構的成員對象,而迭代只能訪問屬於同一個等級結構的成員對象。
訪問者模式也有以下的缺點:
1)增長新的元素類變得困難。每增長一個新的元素意味着要在抽象訪問者角色中增長一個新的抽象操做,並在每個具體訪問者類中添加相應的具體操做。
5、總結
訪問者模式是用來封裝一些施加於某種數據結構之上的操做。它使得能夠在不改變元素自己的前提下增長做用於這些元素的新操做,訪問者模式的目的是把操做從數據結構中分離出來。