訪問者模式是一種較為複雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素具有不同的類型,且不同的訪問者可以對其進行不同的訪問操作 模式動機 對於系統中某些對象,它們存儲在同一個集合中,且具有不同的類型。對於該集合中的對象,可以接受一類稱為訪問者的對象來訪問,不同的訪問者其 ...
訪問者模式是一種較為複雜的行為型設計模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素具有不同的類型,且不同的訪問者可以對其進行不同的訪問操作
模式動機
對於系統中某些對象,它們存儲在同一個集合中,且具有不同的類型。對於該集合中的對象,可以接受一類稱為訪問者的對象來訪問,不同的訪問者其訪問方式有所不同。
在 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集中到一個訪問者對象中,而不是分散到一個個元素類,類的職責更加清晰
- 可以跨過類的等級結構訪問不同等級結構的元素類
- 用戶能夠在不修改現有類層次結構的情況下,定義該類層次結構的新操作
訪問者模式的缺點:
- 增加新的元素類很困難
- 訪問者模式要求訪問者對象訪問並調用每一個元素對象的操作,破壞了封裝性