在軟體開發中,我們經常面臨這樣的場景:一個已經穩定的類層次結構(例如抽象語法樹、文檔對象模型)需要不斷增加新的操作。如果直接在原有類中添加方法,不僅違反「開閉原則」,還會導致類膨脹、職責不清,且每次新增操作都要修改所有相關類,維護成本極高。
訪問者模式正是為了解決這一問題而生。它允許我們在不修改現有類結構的前提下,通過引入「訪問者」來定義作用於對象結構中各元素的新操作。這使得操作與數據結構分離,系統更具擴展性。
根據《設計模式》一書,訪問者模式包含以下五大角色:
visit() 操作接口,參數為具體元素類型,以便訪問者直接調用元素的特定接口。visit() 操作,封裝具體的業務邏輯(如字符串生成、編譯、類型檢查等)。accept(Visitor v) 方法,作為訪問入口。accept(),通常調用 v.visit(this) 實現雙重分派。以下示例展示如何為不可修改的 Flower 層次結構添加「字符串表示」與「蜜蜂授粉」兩種操作:
// 訪問者接口
interface Visitor {
void visit(Gladiolus g);
void visit(Runuculus r);
void visit(Chrysanthemum c);
}
// 元素接口
interface Flower {
void accept(Visitor v);
}
// 具體元素
class Gladiolus implements Flower {
public void accept(Visitor v) { v.visit(this); }
}
class Runuculus implements Flower {
public void accept(Visitor v) { v.visit(this); }
}
class Chrysanthemum implements Flower {
public void accept(Visitor v) { v.visit(this); }
}
// 具體訪問者:生成字符串
class StringVal implements Visitor {
String s;
public String toString() { return s; }
public void visit(Gladiolus g) { s = "Gladiolus"; }
public void visit(Runuculus r) { s = "Runuculus"; }
public void visit(Chrysanthemum c) { s = "Chrysanthemum"; }
}
// 具體訪問者:模擬蜜蜂行為
class Bee implements Visitor {
public void visit(Gladiolus g) { System.out.println("Bee and Gladiolus"); }
public void visit(Runuculus r) { System.out.println("Bee and Runuculus"); }
public void visit(Chrysanthemum c) { System.out.println("Bee and Chrysanthemum"); }
}
// 對象結構(簡化為 List)
List<Flower> flowers = new ArrayList<>();
for (int i = 0; i < 10; i++) flowers.add(FlowerGenerator.newFlower());
// 客戶端使用
Visitor visitor = new StringVal();
for (Flower f : flowers) {
f.accept(visitor);
System.out.println(visitor);
}
訪問者模式的核心在於「雙重分派」:
element.accept(visitor),根據元素的運行時類型分派到具體元素的 accept()。visitor.visit(this),根據訪問者的運行時類型分派到具體訪問者的對應 visit() 重載方法。這巧妙地繞過了 Java 單分派的限制,實現了基於兩個對象類型的動態綁定。
訪問者模式是一個巧妙且強大的模式,適用於「數據結構穩定、操作多變」的場景。它通過雙重分派實現操作與數據的解耦,但代價是增加元素類型的成本較高。在編譯器、AST 處理、文檔導出等領域有廣泛應用。掌握其本質,能幫助你在面對複雜結構操作時做出更優雅的設計決策。