访问者模式

行为型模式

访问次数: 10

访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

类图
classDiagram
    class Visitor {
        <<interface>>
        +visitElementA(element: ConcreteElementA)
        +visitElementB(element: ConcreteElementB)
    }
    class ConcreteVisitor1 {
        +visitElementA(element: ConcreteElementA)
        +visitElementB(element: ConcreteElementB)
    }
    class ConcreteVisitor2 {
        +visitElementA(element: ConcreteElementA)
        +visitElementB(element: ConcreteElementB)
    }
    class Element {
        <<interface>>
        +accept(visitor: Visitor)
    }
    class ConcreteElementA {
        +accept(visitor: Visitor)
        +operationA()
    }
    class ConcreteElementB {
        +accept(visitor: Visitor)
        +operationB()
    }
    class ObjectStructure {
        -elements: List~Element~
        +addElement(element: Element)
        +accept(visitor: Visitor)
    }
    Visitor <|-- ConcreteVisitor1
    Visitor <|-- ConcreteVisitor2
    Element <|-- ConcreteElementA
    Element <|-- ConcreteElementB
    ObjectStructure --> Element
    ConcreteElementA --> Visitor
    ConcreteElementB --> Visitor
源代码示例
// 访问者接口
interface Visitor {
    void visitElementA(ConcreteElementA element);
    void visitElementB(ConcreteElementB element);
}

// 元素接口
interface Element {
    void accept(Visitor visitor);
}

// 具体元素A
class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitElementA(this);
    }
    
    public void operationA() {
        System.out.println("ConcreteElementA operationA");
    }
}

// 具体元素B
class ConcreteElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visitElementB(this);
    }
    
    public void operationB() {
        System.out.println("ConcreteElementB operationB");
    }
}

// 具体访问者1
class ConcreteVisitor1 implements Visitor {
    @Override
    public void visitElementA(ConcreteElementA element) {
        System.out.println("Visitor1 visiting ElementA");
        element.operationA();
    }
    
    @Override
    public void visitElementB(ConcreteElementB element) {
        System.out.println("Visitor1 visiting ElementB");
        element.operationB();
    }
}

// 具体访问者2
class ConcreteVisitor2 implements Visitor {
    @Override
    public void visitElementA(ConcreteElementA element) {
        System.out.println("Visitor2 visiting ElementA");
        element.operationA();
    }
    
    @Override
    public void visitElementB(ConcreteElementB element) {
        System.out.println("Visitor2 visiting ElementB");
        element.operationB();
    }
}

// 对象结构
class ObjectStructure {
    private List<Element> elements = new ArrayList<>();
    
    public void addElement(Element element) {
        elements.add(element);
    }
    
    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}
# Python代码待添加
应用场景
当需要对一个对象结构中的元素执行不同的操作,且这些操作经常变化时。应用场景包括: 1)编译器中的语法树,需要对不同类型的节点执行不同的操作(类型检查、代码生成、优化等) 2)文档处理系统,对不同类型的文档元素执行不同的操作(格式化、转换、验证等) 3)游戏引擎中的场景图,对不同类型的游戏对象执行不同的操作(渲染、物理更新、碰撞检测等) 4)XML/HTML解析器,对不同类型的元素执行不同的操作(解析、验证、转换等) 5)数据库查询优化器,对不同类型的查询节点执行不同的优化操作 6)代码分析工具,对不同类型的代码元素执行不同的分析操作(复杂度分析、依赖分析、重构建议等)
精选场景详解 —— 编译器语法树:对节点施加多类操作
问题背景

语法树节点有表达式、语句、声明等类型。需要对整棵树做类型检查、中间代码生成、优化——若把这些操作都写进节点类,节点会不断膨胀,且每加一种操作都要改所有节点。

模式如何解决
选用访问者:Element(AST 节点)提供 accept(Visitor);Visitor 为每种节点声明 visitXxx。新增「导出 UML」等操作时,主要新增 Visitor 实现,节点类保持稳定。 双分派:accept 里调用 visitor.visit(this),既按节点运行时类型、也按访问者类型分派。 代价:新增节点类型时,所有 Visitor 都要改。适合「节点稳定、操作易变」的编译器/文档工具场景。
场景模型(角色映射)

将模式中的抽象角色映射到该业务领域的具体类:

classDiagram
    class AstNode {
        <<interface>>
        +accept(v: AstVisitor)
    }
    class ExprNode {
        +accept(v: AstVisitor)
    }
    class StmtNode {
        +accept(v: AstVisitor)
    }
    class AstVisitor {
        <<interface>>
        +visitExpr(node: ExprNode)
        +visitStmt(node: StmtNode)
    }
    class TypeCheckVisitor {
        +visitExpr(node: ExprNode)
        +visitStmt(node: StmtNode)
    }
    class CodeGenVisitor {
        +visitExpr(node: ExprNode)
        +visitStmt(node: StmtNode)
    }
    class Compiler {
        +compile(root: AstNode)
    }
    AstNode <|.. ExprNode
    AstNode <|.. StmtNode
    AstVisitor <|.. TypeCheckVisitor
    AstVisitor <|.. CodeGenVisitor
    ExprNode ..> AstVisitor : accept
    Compiler --> AstNode
    Compiler --> AstVisitor