首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 設計模式精講:訪問者模式與雙重分派機制深度解析

     編輯:Java 相關知識

模式動機

當需要對一個對象結構(如抽象語法樹、文檔對象模型)中的元素執行多種不相關操作(編譯、優化、打印、序列化)時,若直接在元素類中添加方法,會導致類膨脹且違反單一職責。訪問者模式將操作從元素類中分離,封裝到獨立的訪問者類中,新增操作無需修改元素類(開閉原則)。

核心難點:雙重分派

Java 只有單分派(動態綁定僅基於接收者類型)。訪問者模式通過兩次單分派模擬雙重分派:

  1. 客戶端調用 element.accept(visitor) —— 第一次分派,基於 element 實際類型調用具體 accept。
  2. accept 內部調用 visitor.visit(this) —— 第二次分派,基於 visitor 實際類型調用具體 visit(ConcreteElement)。

最終執行的代碼同時取決於 Element 和 Visitor 的具體類型。

角色與類圖

角色接口/類關鍵方法
Visitor (抽象訪問者)interfacevisit(ConcreteElementA), visit(ConcreteElementB)
ConcreteVisitor (具體訪問者)class實現各 visit,封裝一類操作邏輯
Element (抽象元素)abstract classaccept(Visitor v)
ConcreteElement (具體元素)class實現 accept → v.visit(this),暴露自身特有方法
ObjectStructure (對象結構)class管理元素集合,提供 action(Visitor) 遍歷調用 accept

代碼實現

1. 訪問者接口

public interface Visitor {
    void visit(NodeA node);
    void visit(NodeB node);
}

2. 具體訪問者

public class VisitorA implements Visitor {
    @Override public void visit(NodeA node) { System.out.println("VisitorA 處理 " + node.operationA()); }
    @Override public void visit(NodeB node) { System.out.println("VisitorA 處理 " + node.operationB()); }
}
public class VisitorB implements Visitor {
    @Override public void visit(NodeA node) { System.out.println("VisitorB 處理 " + node.operationA()); }
    @Override public void visit(NodeB node) { System.out.println("VisitorB 處理 " + node.operationB()); }
}

3. 元素結構

public abstract class Node {
    public abstract void accept(Visitor visitor);
}
public class NodeA extends Node {
    @Override public void accept(Visitor v) { v.visit(this); }
    public String operationA() { return "NodeA 業務邏輯"; }
}
public class NodeB extends Node {
    @Override public void accept(Visitor v) { v.visit(this); }
    public String operationB() { return "NodeB 業務邏輯"; }
}

4. 對象結構

public class ObjectStructure {
    private List<Node> nodes = new ArrayList<>();
    public void add(Node node) { nodes.add(node); }
    public void action(Visitor visitor) {
        for (Node node : nodes) node.accept(visitor);
    }
}

5. 客戶端

public class Client {
    public static void main(String[] args) {
        ObjectStructure os = new ObjectStructure();
        os.add(new NodeA());
        os.add(new NodeB());
        Visitor v = new VisitorA();
        os.action(v); // 輸出 VisitorA 處理 NodeA/NodeB
    }
}

時序圖解析

Client → ObjectStructure.action(VisitorA)
    → NodeA.accept(VisitorA)          // 第一次分派
        → VisitorA.visit(NodeA)       // 第二次分派
            → NodeA.operationA()
    → NodeB.accept(VisitorA)
        → VisitorA.visit(NodeB)
            → NodeB.operationB()

優缺點

優點缺點
擴展性極強:新增操作只需新增 Visitor 子類,元素類零修改元素類結構變動困難:新增/刪除 Element 需修改 Visitor 接口及所有實現類
復用性好:將相關行為集中在一個 Visitor 中破壞封裝:Visitor 需訪問元素內部狀態,元素需暴露公共方法
分離關注點:編譯、打印、類型檢查等邏輯物理隔離不適用於頻繁變動的元素結構

適用場景

  • 對象結構穩定(如 AST、DOM、圖形編輯器圖元),但操作頻繁擴展。
  • 需要對結構中不同類型對象進行類型相關操作,避免 instanceof 判斷鏈。
  • 編譯器前端(語法樹遍歷)、文檔導出(HTML/PDF/Word)、報表生成。

實戰變體:帶狀態的訪問者

public class TypeCheckVisitor implements Visitor {
    private SymbolTable symTab = new SymbolTable();
    private List<Error> errors = new ArrayList<>();
    // visit 方法中可訪問/修改 symTab、errors
    public List<Error> getErrors() { return errors; }
}

訪問者可攜帶上下文狀態(符號表、錯誤列表、生成代碼緩衝區),實現複雜的多遍處理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved