訪問者模式將數據結構(對象結構)與作用於其上的操作分離,使得在不修改數據類的前提下動態添加新操作。核心雙分派機制:Element.accept(Visitor) → Visitor.visitConcreteElement(this)。
List<Object>)進行類型相關處理,避免大量 instanceof 判斷。// 訪問者接口:為每種具體元素聲明 visit 方法
public interface Visitor {
void visitCollection(Collection c);
void visitString(String s);
void visitFloat(Float f);
}
// 可訪問元素接口:定義 accept 入口
public interface Visitable {
void accept(Visitor v);
}
public class ConcreteElement implements Visitable {
private String value;
public ConcreteElement(String v) { this.value = v; }
public void accept(Visitor v) { v.visitString(this); }
public String getValue() { return value; }
}
public class PrintVisitor implements Visitor {
public void visitCollection(Collection c) {
for (Object o : c) {
if (o instanceof Visitable) ((Visitable)o).accept(this);
}
}
public void visitString(String s) { System.out.println("'" + s + "'"); }
public void visitFloat(Float f) { System.out.println(f + "f"); }
}
List<Visitable> list = Arrays.asList(
new ConcreteElement("hello"),
new ConcreteElement("world")
);
Visitor visitor = new PrintVisitor();
for (Visitable e : list) e.accept(visitor);
| 優點 | 缺點 |
|---|---|
| 符合開閉原則:新增操作只需新增 Visitor 實現,不動 Element | 新增 Element 類型需修改 Visitor 接口及所有實現,違反開閉原則 |
| 將相關操作集中在 Visitor 中,職責單一 | 破壞封裝:Visitor 需訪問 Element 內部狀態,可能需暴露 getter |
| 適合對象結構共享多種操作(編譯器、文檔模型) | 雙分派增加理解難度,調試較繁瑣 |
利用反射動態查找 visitXxx 方法,使 Visitor 接口無需預先聲明所有類型:
public class ReflectiveVisitor {
public void visit(Object element) {
String methodName = "visit" + element.getClass().getSimpleName();
try {
Method m = getClass().getMethod(methodName, element.getClass());
m.invoke(this, element);
} catch (NoSuchMethodException e) {
defaultVisit(element);
} catch (Exception e) { throw new RuntimeException(e); }
}
protected void defaultVisit(Object o) { /* 默認處理 */ }
}
訪問者模式是「操作與數據分離」的經典實踐。在「數據結構穩定、操作多變」場景下極具價值;若數據類型頻繁變更,傾向於在數據類內部定義操作或使用反射增強版。