行為型模式之訪問者模式
目錄
在現實生活中,有些集合物件中存在多種不同的元素,且每種元素也存在多種不同的訪問者和處理方式。例如,公園中存在多個景點,也存在多個遊客,不同的遊客對同一個景點的評價可能不同。
這些被處理的資料元素相對穩定而訪問方式多種多樣的資料結構,如果用“訪問者模式”來處理比較方便。
訪問者模式能把處理方法從資料結構中分離出來,並可以根據需要增加新的處理方法,且不用修改原來的程式程式碼與資料結構,這提高了程式的擴充套件性和靈活性。
定義與特點
訪問者(Visitor)模式的定義:將作用於某種資料結構中的各元素的操作分離出來封裝成獨立的類,使其在不改變資料結構的前提下可以新增作用於這些元素的新的操作,為資料結構中的每個元素提供多種訪問方式。它將對資料的操作與資料結構進行分離,是行為類模式中最複雜的一種模式
訪問者(Visitor)模式是一種物件行為型模式,其主要優點如下:
- 擴充套件性好:能夠在不修改物件結構中的元素的情況下,為物件結構中的元素新增新的功能。
- 複用性好:可以通過訪問者來定義整個物件結構通用的功能,從而提高系統的複用程度。
- 靈活性好:訪問者模式將資料結構與作用於結構上的操作解耦,使得操作集合可相對自由地演化而不影響系統的資料結構。
- 符合單一職責原則:訪問者模式把相關的行為封裝在一起,構成一個訪問者,使每一個訪問者的功能都比較單一。
訪問者(Visitor)模式的主要缺點如下:
- 增加新的元素類很困難:在訪問者模式中,每增加一個新的元素類,都要在每一個具體訪問者類中增加相應的具體操作,這違背了“開閉原則”。
- 破壞封裝:訪問者模式中具體元素對訪問者公佈細節,這破壞了物件的封裝性。
- 違反了依賴倒置原則:訪問者模式依賴了具體類,而沒有依賴抽象類。
結構與實現
訪問者(Visitor)模式實現的關鍵是如何將作用於元素的操作分離出來封裝成獨立的類,其基本結構與實現方法如下。
模式的結構
訪問者模式包含以下主要角色:
- 抽象訪問者(Visitor)角色:定義一個訪問具體元素的介面,為每個具體元素類對應一個訪問操作 Visit() ,該操作中的引數型別標識了被訪問的具體元素。
- 具體訪問者(ConcreteVisitor)角色:實現抽象訪問者角色中宣告的各個訪問操作,確定訪問者訪問一個元素時該做什麼。
- 抽象元素(Element)角色
- 具體元素(ConcreteElement)角色:實現抽象元素角色提供的 Accept() 操作,其方法體通常都是 visitor.Visit(this) ,另外具體元素中可能還包含本身業務邏輯的相關操作。
- 物件結構(Object Structure)角色:是一個包含元素角色的容器,提供讓訪問者物件遍歷容器中的所有元素的方法,通常由 List、Set、Map 等聚合類實現。
其結構圖如圖所示:
模式的實現
訪問者模式的實現程式碼如下:
class Program
{
static void Main(string[] args)
{
ObjectStructure os=new ObjectStructure();
os.Add(new ConcreteElementA());
os.Add(new ConcreteElementB());
IVisitor visitor=new ConcreteVisitorA();
os.Accept(visitor);
Console.WriteLine("------------------------");
visitor=new ConcreteVisitorB();
os.Accept(visitor);
Console.Read();
}
}
//抽象訪問者
public interface IVisitor
{
void Visit(ConcreteElementA element);
void Visit(ConcreteElementB element);
}
//具體訪問者A類
public class ConcreteVisitorA : IVisitor
{
public void Visit(ConcreteElementA element)
{
Console.WriteLine("具體訪問者A訪問-->"+element.OperationA());
}
public void Visit(ConcreteElementB element)
{
Console.WriteLine("具體訪問者A訪問-->"+element.OperationB());
}
}
//具體訪問者B類
public class ConcreteVisitorB : IVisitor
{
public void Visit(ConcreteElementA element)
{
Console.WriteLine("具體訪問者B訪問-->"+element.OperationA());
}
public void Visit(ConcreteElementB element)
{
Console.WriteLine("具體訪問者B訪問-->"+element.OperationB());
}
}
//抽象元素類
public interface IElement
{
void Accept(IVisitor visitor);
}
//具體元素A類
public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
public String OperationA()
{
return "具體元素A的操作。";
}
}
//具體元素B類
public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
public String OperationB()
{
return "具體元素B的操作。";
}
}
//物件結構角色
public class ObjectStructure
{
private List<IElement> list=new List<IElement>();
public void Accept(IVisitor visitor)
{
foreach (var item in list)
{
item.Accept(visitor);
}
}
public void Add(IElement element)
{
list.Add(element);
}
public void Remove(IElement element)
{
list.Remove(element);
}
}
程式的執行結果如下:
具體訪問者A訪問-->具體元素A的操作。
具體訪問者A訪問-->具體元素B的操作。
------------------------
具體訪問者B訪問-->具體元素A的操作。
具體訪問者B訪問-->具體元素B的操作。
應用場景
通常在以下情況可以考慮使用訪問者(Visitor)模式:
- 物件結構相對穩定,但其操作演算法經常變化的程式。
- 物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構。
- 物件結構包含很多型別的物件,希望對這些物件實施一些依賴於其具體型別的操作。
擴充套件:與迭代器模式、組合模式聯用
訪問者(Visitor)模式是使用頻率較高的一種設計模式,它常常同以下兩種設計模式聯用:
-
與“迭代器模式”聯用:因為訪問者模式中的“物件結構”是一個包含元素角色的容器,當訪問者遍歷容器中的所有元素時,常常要用迭代器。
注:現在一般的高階語言都自帶的有迭代器,不需要自己實現。 -
訪問者(Visitor)模式同“組合模式”聯用:因為訪問者(Visitor)模式中的“元素物件”可能是葉子物件或者是容器物件,如果元素物件包含容器物件,就必須用到組合模式,其結構圖如圖所示: