~/app/articles/NORMAL

cat article/34.md

访问者模式 (Visitor) 全面讲解

学习笔记
设计模式
访问者模式
面向对象
Java
软件开发
编程模式
行为型设计模式

本文全面讲解了访问者模式,包括其定义、核心思想、适用场景、与传统方式的对比以及模式结构。通过实例和类比,深入浅出地介绍了如何使用访问者模式来分离算法与对象结构,提高代码的可扩展性和可维护性。

date: 2025-06-22T22:24:00 tags:

  • 复习
  • 面向对象
  • 设计模式
  • Java

访问者模式 (Visitor) 全面讲解


1. 什么是访问者模式?

访问者模式是行为型设计模式,用于将算法与对象结构分离,使得在不修改对象结构的前提下,能为对象结构中的元素添加新操作。

  • 核心思想:通过**双重分发(Double Dispatch)**机制,让操作可以独立变化。
  • 类比:
    • 医生(Visitor)为不同病人(Element)开具不同药方(visit方法)。
    • 文件系统遍历(对文件和文件夹执行不同操作)。

🔹 适用场景

  • 需要对一个复杂对象结构(如AST、UI组件树)执行多种不相关的操作。
  • 对象结构稳定,但经常需要新增操作(如编译器中的语法分析、优化、代码生成)。

2. 为什么需要访问者模式?

问题场景(传统方式的缺陷)

假设有一个文档对象模型(DOM),包含TextNode和ElementNode:

interface Node {
    // 传统方式:每个操作需修改Node接口
    void highlight();
    void extractText();
}

class TextNode implements Node { /*...*/ }
class ElementNode implements Node { /*...*/ }

问题:每新增一个操作(如compress()),需修改所有Node实现类,违反开闭原则!

解决方案

将操作抽取为独立的访问者,通过accept()方法动态绑定具体操作。


3. 访问者模式的结构

角色说明

  1. Visitor(访问者接口):声明访问各类元素的visit方法(如NodeVisitor)。
  2. ConcreteVisitor(具体访问者):实现具体操作(如HighlightVisitor)。
  3. Element(元素接口):定义accept(Visitor)方法(如Node)。
  4. ConcreteElement(具体元素):实现accept方法(如TextNode)。
  5. ObjectStructure(对象结构):维护元素集合(如文档树)。

4. 示例代码(文档处理系统)

步骤1:定义元素接口

interface Node {
    void accept(NodeVisitor visitor); // 关键:接收访问者
}

步骤2:实现具体元素

// 文本节点
class TextNode implements Node {
    private String content;

    public TextNode(String content) {
        this.content = content;
    }

    @Override
    public void accept(NodeVisitor visitor) {
        visitor.visit(this); // 调用访问者的visit(TextNode)方法
    }

    public String getContent() {
        return content;
    }
}

// 元素节点
class ElementNode implements Node {
    private String tagName;

    public ElementNode(String tagName) {
        this.tagName = tagName;
    }

    @Override
    public void accept(NodeVisitor visitor) {
        visitor.visit(this); // 调用访问者的visit(ElementNode)方法
    }

    public String getTagName() {
        return tagName;
    }
}

步骤3:定义访问者接口

interface NodeVisitor {
    void visit(TextNode node); // 重载方法
    void visit(ElementNode node);
}

步骤4:实现具体访问者

// 高亮访问者
class HighlightVisitor implements NodeVisitor {
    @Override
    public void visit(TextNode node) {
        System.out.println("高亮文本: " + node.getContent());
    }

    @Override
    public void visit(ElementNode node) {
        System.out.println("高亮元素: <" + node.getTagName() + ">");
    }
}

// 文本提取访问者(新增操作无需修改Node类)
class TextExtractVisitor implements NodeVisitor {
    @Override
    public void visit(TextNode node) {
        System.out.println("提取文本: " + node.getContent());
    }

    @Override
    public void visit(ElementNode node) {
        System.out.println("跳过元素: <" + node.getTagName() + ">");
    }
}

步骤5:客户端调用

public class Client {
    public static void main(String[] args) {
        // 构建对象结构
        List<Node> nodes = Arrays.asList(
            new TextNode("Hello"),
            new ElementNode("div")
        );

        // 执行高亮操作
        NodeVisitor highlighter = new HighlightVisitor();
        for (Node node : nodes) {
            node.accept(highlighter);
        }

        // 执行文本提取操作
        NodeVisitor extractor = new TextExtractVisitor();
        for (Node node : nodes) {
            node.accept(extractor);
        }
    }
}

输出:

高亮文本: Hello  
高亮元素: <div>  
提取文本: Hello  
跳过元素: <div>  

5. 关键点总结

🔹 双重分发(Double Dispatch)

  • 第一次分发:元素调用accept(Visitor)。
  • 第二次分发:访问者调用visit(ConcreteElement)。

🔹 优点

  • 符合开闭原则:新增操作只需添加访问者,无需修改元素类。
  • 集中相关操作:将分散的操作聚合到访问者中。

🔹 缺点

  • 元素接口需稳定(新增元素类型需修改所有访问者)。

6. 互动实践

问题1:以下代码缺少什么关键部分?

interface Animal { /*...*/ }
class Dog implements Animal { /*...*/ }

class FeedingVisitor {
    void feed(Dog dog) { /*...*/ }
}

答案:缺少Animal的accept(Visitor)方法和Visitor接口定义。

问题2:如何用访问者模式实现购物车折扣计算?

任务:

  • 元素:Book(价格100)、Fruit(价格50,重量2kg)。
  • 访问者:DiscountVisitor(书打9折,水果按重量计算)。
  • 补全代码:
class DiscountVisitor implements ItemVisitor {
    @Override
    public void visit(Book book) { /* 计算书折扣 */ }
    // 补全visit(Fruit)方法
}

7. 考试练习题

题目1(概念题)

访问者模式的核心机制是?
A. 单分派
B. 双重分发
C. 动态代理
D. 反射

答案:B


题目2(代码填空)

补全访问者模式的代码:

interface ShapeVisitor {
    void visit(Circle circle);
    void visit(Rectangle ____);  // 填空1
}

class Circle implements Shape {
    @Override
    public void accept(ShapeVisitor visitor) {
        ____.visit(this);  // 填空2
    }
}

答案:

  1. rectangle
  2. visitor

题目3(设计题)

用访问者模式设计编译器语法树(AST)处理:

  • 元素:VariableNode、FunctionCallNode。
  • 访问者:TypeCheckVisitor、CodeGenVisitor。
    要求:画出类图并写出关键代码。
$ comments --list (0)

// not signed in


// no comments yet