Java 集合(Collection、List、Map)存儲的是 Object 參考。取出元素時,編譯期類型資訊丟失,若要針對不同類型執行不同邏輯,往往陷入 instanceof + 強制轉型的「判斷地獄」:
// 反模式:充滿 instanceof 的處理邏輯
Iterator it = collection.iterator();
while (it.hasNext()) {
Object o = it.next();
if (o instanceof Collection) handleCollection((Collection)o);
else if (o instanceof String) handleString((String)o);
else if (o instanceof Float) handleFloat((Float)o);
else handleDefault(o);
}
缺點:違反開閉原則——新增類型需修改遍歷邏輯;職責分散——同一類型的處理邏輯分散在各處。
將資料結構(元素)與作用於其上的操作(訪問者)分離。在不修改元素類別的前提下,定義新的操作。
訪問者模式利用雙重分派實現「元素類型 + 訪問者類型」決定具體執行邏輯:
element.accept(visitor) —— 第一次分派,綁定具體元素類型visitor.visitConcreteElement(this) —— 第二次分派,綁定具體訪問者類型| 角色 | 介面/類別 | 關鍵方法 |
|---|---|---|
| Visitor | 介面 | visitConcreteElementA(A), visitConcreteElementB(B)... |
| ConcreteVisitor | 實作類 | 實現各元素的具體操作邏輯 |
| Visitable (Element) | 介面 | accept(Visitor) |
| ConcreteElement | 實作類 | 實現 accept 呼叫對應的 visitXxx |
| ObjectStructure | 類別 | 管理元素集合,提供遍歷入口 |
public interface Visitor {
void visitCollection(Collection collection);
void visitString(String string);
void visitFloat(Float floatVal);
}
public interface Visitable {
void accept(Visitor visitor);
}
public class ConcreteElement implements Visitable {
private String value;
public ConcreteElement(String value) { this.value = value; }
public String getValue() { return value; }
@Override
public void accept(Visitor visitor) {
visitor.visitString(this); // 關鍵:雙重分派第二步
}
}
// 同理可定義 CollectionElement、FloatElement 等
public class ConcreteVisitor implements Visitor {
@Override
public void visitCollection(Collection collection) {
Iterator it = collection.iterator();
while (it.hasNext()) {
Object o = it.next();
if (o instanceof Visitable) { // 僅需判斷是否可訪問
((Visitable) o).accept(this);
}
}
}
@Override
public void visitString(String string) {
System.out.println("'" + string + "'");
}
@Override
public void visitFloat(Float floatVal) {
System.out.println(floatVal + "f");
}
}
List elements = Arrays.asList(
new ConcreteElement("Hello"),
new ConcreteElement("World"),
new FloatElement(3.14f)
);
Visitor visitor = new ConcreteVisitor();
for (Visitable e : elements) {
e.accept(visitor);
}
// 輸出:'Hello' 'World' 3.14f
| 適用 | 不適用 |
|---|---|
| 元素類型穩定(很少新增),但操作頻繁擴展 | 元素類型頻繁新增(每新增一類型需修改 Visitor 介面及所有實作) |
| 需對結構中不同類型物件執行不相關操作 | 操作簡單、數量少,直接在元素類別加方法更清晰 |
| 物件結構複雜(樹狀、圖狀),需統一遍歷邏輯 | 結構單純(單層 List),for-each 即可 |
當元素類型也頻繁變動時,可利用 Java 反射動態查找 visitXxx 方法,避免修改 Visitor 介面:
public class ReflectiveVisitor implements Visitor {
@Override
public void visit(Object element) { // 單一通用入口
try {
Method method = getClass().getMethod("visit" + element.getClass().getSimpleName(), element.getClass());
method.invoke(this, element);
} catch (NoSuchMethodException e) {
defaultVisit(element); // 找不到對應方法時的預設處理
} catch (Exception e) { throw new RuntimeException(e); }
}
// 保留具體 visitXxx 方法供反射調用
public void visitString(String s) { System.out.println("'" + s + "'"); }
public void visitInteger(Integer i) { System.out.println(i + "i"); }
// ...
}
訪問者模式是「資料結構穩定、操作多變」場景的終極解法。它以雙重分派實現編譯期類型安全的操作分發,將散落在各處的 instanceof 邏輯集中到訪問者類別中。雖然引入了介面與類別的複雜度,但換來的是極佳的擴展性與職責清晰度。現代 Java 結合反射或模式匹配,可進一步降低其剛性限制。