自学教程 学编程从这里开始

访问者模式

《访问者模式》|设计模式系列第21篇

系列:行为型模式 · 访问者模式(Visitor Pattern)
关键词:双重分派、数据结构与操作分离、新增访问者、稳定元素结构

一、什么是访问者模式

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

属于行为型设计模式。

简单理解:
把数据元素结构和作用在元素上的操作解耦。元素类提供一个 accept 方法接收访问者对象;访问者内部针对每一种元素类型重载 visit 方法。当需要新增操作时,只需要新增访问者类,不用修改元素代码。

适用前提:元素结构稳定很少变动;经常需要新增操作。
例子:文档包含 Paragraph、Image 元素;可以新增 HtmlExportVisitor、PdfExportVisitor,实现不同导出逻辑,不用修改文档元素类。

核心意图
将操作从元素类抽离到访问者;元素结构稳定,操作频繁扩展。

适用场景

  1. 对象结构稳定,很少增删元素类型,但需要经常新增操作
  2. 需要对不同类型元素执行一系列不相关操作
  3. 需要跨类型元素做统计、导出、报表等操作
  4. 编译器语法树遍历、AST处理

⚠️ 缺点预警:元素结构一旦变化(新增/删除元素),所有访问者都需要同步修改;破坏元素封装,访问者需要访问元素内部数据。

二、UML结构图

角色划分:

  1. Visitor(访问者抽象):为每一种具体元素定义visit重载方法
  2. ConcreteVisitor(具体访问者):实现visit逻辑,对不同元素执行对应操作
  3. Element(元素抽象):声明accept方法,接收Visitor
  4. ConcreteElement(具体元素):实现accept,调用visitor.visit(this)
  5. ObjectStructure(对象结构):维护元素集合,提供遍历元素的方法
  6. Client(客户端):构造对象结构,创建访问者,触发遍历

三、Java 完整示例

场景:Computer 结构,包含 Keyboard、Monitor、Mouse;
Visitor:PriceVisitor 统计总价,NameVisitor 打印组件名称。
代码全部使用英文。

1. Visitor 抽象访问者

public interface Visitor {
    void visit(Keyboard keyboard);
    void visit(Monitor monitor);
    void visit(Mouse mouse);
}

2. Element 抽象元素

public interface ComputerPart {
    void accept(Visitor visitor);
}

3. ConcreteElement 具体元素

public class Keyboard implements ComputerPart {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class Monitor implements ComputerPart {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class Mouse implements ComputerPart {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

4. ObjectStructure 对象结构 Computer

import java.util.Arrays;
import java.util.List;

public class Computer implements ComputerPart {
    private List<ComputerPart> parts;

    public Computer() {
        parts = Arrays.asList(new Keyboard(), new Monitor(), new Mouse());
    }

    @Override
    public void accept(Visitor visitor) {
        for(ComputerPart part : parts){
            part.accept(visitor);
        }
    }
}

5. ConcreteVisitor 具体访问者

public class PriceVisitor implements Visitor {
    private double total = 0;

    @Override
    public void visit(Keyboard keyboard) {
        total += 80;
    }

    @Override
    public void visit(Monitor monitor) {
        total += 800;
    }

    @Override
    public void visit(Mouse mouse) {
        total += 50;
    }

    public double getTotalPrice() {
        return total;
    }
}

public class NameVisitor implements Visitor {
    @Override
    public void visit(Keyboard keyboard) {
        System.out.println("Computer part: Keyboard");
    }

    @Override
    public void visit(Monitor monitor) {
        System.out.println("Computer part: Monitor");
    }

    @Override
    public void visit(Mouse mouse) {
        System.out.println("Computer part: Mouse");
    }
}

6. Client 客户端

public class Client {
    public static void main(String[] args) {
        ComputerPart computer = new Computer();

        NameVisitor nameVisitor = new NameVisitor();
        computer.accept(nameVisitor);

        PriceVisitor priceVisitor = new PriceVisitor();
        computer.accept(priceVisitor);
        System.out.println("Total price: " + priceVisitor.getTotalPrice());
    }
}

运行输出:

Computer part: Keyboard
Computer part: Monitor
Computer part: Mouse
Total price: 930.0

四、优缺点总结

✅ 优点

  1. 新增操作只需要新增访问者,元素类无需改动,符合开闭原则
  2. 相关操作集中在同一个访问者类,便于管理
  3. 可以跨不同元素类型执行统一遍历逻辑

❌ 缺点

  1. 如果新增元素类型,所有访问者都要新增visit方法,违反开闭原则
  2. 访问者需要访问元素内部数据,破坏封装
  3. Java基于重载实现双重分派,可读性理解成本高

五、框架源码应用

  • JDK AST 抽象语法树访问
  • Spring BeanDefinitionVisitor
  • MyBatis SqlNode Visitor

六、小结

访问者模式核心:元素结构稳定,操作经常变化;通过accept+visit双重分派,把操作逻辑剥离到访问者。
⚠️ 非常重要:元素类型一旦经常增加,严禁使用访问者。日常业务开发使用频率很低,多用于编译器、解析树等固定结构场景。

标签:

0 条笔记