訪問者模式是一種較為複雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素具有不同的型別,且不同的訪問者可以對其進行不同的訪問操作
模式動機
對於系統中某些物件,它們儲存在同一個集合中,且具有不同的型別。對於該集合中的物件,可以接受一類稱為訪問者的物件來訪問,不同的訪問者其訪問方式有所不同。
在 Java 等面嚮物件語言中都提供了大量用於儲存多個元素的集合物件,集合中儲存的物件有時候是同一型別,有時候不是同一型別,或許它們只是具有共同的父類。假如我們要針對一個包含不同型別元素的集合採取某種操作,而操作細節根據元素型別不同而不同,就會出現大量型別判斷語句,增大程式碼複雜度。
實際使用時,對相同元素的物件也可能存在多種不同的操作方式,而且可能還需要增加新的操作,此時訪問者模式是一個值得考慮的解決方案。
模式定義
表示一個作用於某物件結構中的各元素的操作,它使我們可以在不改變各元素的類的前提下定義作用於這些元素的新操作。訪問者模式是一種物件行為型模式。
Represent an operation to be performedd on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
模式分析
訪問者模式結構較為複雜,首先看一張模式結構類圖
物件結構(ObjectStructure)是一個元素集合,儲存了不同型別的元素物件,以供不同訪問者訪問。訪問者模式包括兩個層次,一個是訪問者層次結構,另一個是元素層次結構。
訪問者層次結構提供了抽象訪問者(Visitor)和具體訪問者(ConcreteVisitor)。抽象訪問者宣告瞭訪問元素物件的方法,通常為每一種型別的元素物件都提供一個訪問方法,而具體訪問者可以實現這些訪問方法。
這些訪問方法的設計又有兩種,一種是直接在方法名中標明待訪問元素物件的型別,如 visitConcreteElementA(ConcreteElementA elementA),還有一種是統一取名為 visit(),通過引數型別的不同來定義一系列過載方法。
public abstract class Visitor {
// 統一取名
public abstract void visit(ConcreteElementA elementA);
public abstract void visit(ConcreteElementB elementB);
// 如果所有訪問者對某一型別的元素訪問操作都相同
// 則可以將操作程式碼移到抽象訪問者中
public void visit(ConcreteElementC elementC) {
...
}
}
元素層次結構提供了抽象元素類(Element)和具體元素類(ConcreteElementA),抽象元素類一般都宣告一個 accept() 方法,用於接受訪問者的訪問。該方法傳入一個抽象訪問者 Visitor 型別的引數,在程式執行時確定其具體訪問者的型別,並呼叫具體訪問者物件的 visit() 方法實現對元素物件的操作
public class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
// 在具體元素類中可以定義不同型別的元素所特有的業務方法
}
}
具體元素類 ConcreteElementA 的 accept() 方法通過呼叫 Visitor 類的 visit() 方法實現對元素的訪問,並以當前物件作為 visit() 方法的引數,這種呼叫機制也稱“雙重分派”。正因為使用了雙重分派技術,使得增加新的訪問者無須修改現有類庫程式碼,只需將新的訪問者物件傳入具體元素物件的 accept() 方法即可,程式執行時將回撥在 Visitor 類中定義的 visit() 方法,從而實現不同形式的訪問。
物件結構(ObjectStructure)是一個集合,用於儲存元素物件並接受訪問者的訪問。在物件結構中可以使用迭代器對儲存在集合中的元素物件進行遍歷,並逐個呼叫每一個物件的 accept() 方法,實現對元素物件的訪問操作。
public class ObjectStructure {
private ArrayList list = new ArrayList();
public void accept(Visitor visitor) {
Iterator i = list.iterator();
while(i.hashNext()) {
((Element)i.next()).accept(visitor);
}
}
public void addElement(Element element) {
list.add(element);
}
public void removeElement(Element element) {
list.remove(element);
}
}
最終在客戶端我們需要例項化一個物件結構物件,並向其新增元素物件,再呼叫 accept() 方法來接受訪問者物件的訪問。具體訪問者型別可以通過配置檔案來確定。
public class Client {
public static void main(String[] args) {
Element elementA = new ElementA();
Element elementB = new ElementB();
Element elementC = new ElementC();
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(elementA);
objectStructure.addElement(elementB);
objectStructure.addElement(elementC);
Visitor visitor = new ConcreteVisitorA();
objectStructure.accept(visitor);
}
}
如果需要修改訪問者型別,只或者增加新的型別的訪問者,只需修改配置檔案即可,符合開閉原則。但如果要增加新的型別的具體元素類,則訪問者類需要為其定義新的訪問方法,從這一點看又違背了開閉原則。
模式優缺點
訪問者模式的優點:
- 使得增加新的訪問操作變得容易,無須修改現有類庫的程式碼
- 將有關類物件的訪問行為i集中到一個訪問者物件中,而不是分散到一個個元素類,類的職責更加清晰
- 可以跨過類的等級結構訪問不同等級結構的元素類
- 使用者能夠在不修改現有類層次結構的情況下,定義該類層次結構的新操作
訪問者模式的缺點:
- 增加新的元素類很困難
- 訪問者模式要求訪問者物件訪問並呼叫每一個元素物件的操作,破壞了封裝性