【设计模式】行为型-访问者模式

文章目录

  • 前言
  • 一、概念
  • 二、核心结构
  • [三、Java 代码实现(文件系统:文件+文件夹)](#三、Java 代码实现(文件系统:文件+文件夹))
    • [1. 抽象元素(FileSystemElement)](#1. 抽象元素(FileSystemElement))
    • [2. 具体元素:文件 File](#2. 具体元素:文件 File)
    • [3. 具体元素:文件夹 Folder](#3. 具体元素:文件夹 Folder)
    • [4. 抽象访问者 Visitor](#4. 抽象访问者 Visitor)
    • [5. 具体访问者1:打印功能](#5. 具体访问者1:打印功能)
    • [6. 具体访问者2:计算总大小](#6. 具体访问者2:计算总大小)
    • [7. 客户端](#7. 客户端)
  • 四、双分派(核心机制)
  • 五、优缺点
  • 六、应用场景
  • [七、访问者 VS 迭代器 VS 组合](#七、访问者 VS 迭代器 VS 组合)
  • 八、总结

前言

在开发中,我们经常遇到稳定的数据结构 + 多变的操作逻辑 场景:比如一个树形结构(文件、订单、商品),需要新增打印、压缩、审计、报表 等不同功能。如果每次加功能都去修改元素类,会严重违背开闭原则。访问者模式 就是专门解决稳定结构、多变操作 的行为型设计模式,让你不修改元素,就能无限扩展新功能


一、概念

访问者模式(Visitor Pattern) 是一种行为型设计模式 ,核心思想:
封装作用于某种数据结构(如List、Set、Map、组合树)中各元素的操作,在不改变元素类的前提下,定义作用于这些元素的新操作。

简单理解:

  • 数据结构(元素)稳定不变
  • 功能(访问者)可以无限扩展
  • 操作从元素类中抽离出来,交给访问者
  • 双分派:元素接受访问,访问者处理元素

一句话总结:
结构不动,功能扩展;数据归你,操作归我。


二、核心结构

  1. Visitor(抽象访问者)
    为每类元素声明一个访问方法。
  2. ConcreteVisitor(具体访问者)
    实现具体操作逻辑(打印、报表、压缩等)。
  3. Element(抽象元素)
    提供accept(Visitor)方法,接受访问。
  4. ConcreteElement(具体元素)
    实现accept,允许访问者访问自己。
  5. ObjectStructure(对象结构)
    持有元素集合,可遍历让访问者访问。

三、Java 代码实现(文件系统:文件+文件夹)

结构:文件夹(组合)+ 文件(叶子)

功能:打印、提取大小(两个访问者)

1. 抽象元素(FileSystemElement)

java 复制代码
public interface FileSystemElement {
    void accept(Visitor visitor);
}

2. 具体元素:文件 File

java 复制代码
public class File implements FileSystemElement {
    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this); // 双分派核心
    }

    // getter 给访问者使用
    public String getName() { return name; }
    public int getSize() { return size; }
}

3. 具体元素:文件夹 Folder

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Folder implements FileSystemElement {
    private String name;
    private List<FileSystemElement> children = new ArrayList<>();

    public Folder(String name) { this.name = name; }

    public void add(FileSystemElement e) { children.add(e); }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
        // 递归让子元素接受访问
        for (FileSystemElement e : children) e.accept(visitor);
    }

    public String getName() { return name; }
    public List<FileSystemElement> getChildren() { return children; }
}

4. 抽象访问者 Visitor

java 复制代码
public interface Visitor {
    void visit(File file);
    void visit(Folder folder);
}

5. 具体访问者1:打印功能

java 复制代码
public class PrintVisitor implements Visitor {
    @Override
    public void visit(File file) {
        System.out.println("文件:" + file.getName() + " 大小:" + file.getSize());
    }

    @Override
    public void visit(Folder folder) {
        System.out.println("文件夹:" + folder.getName());
    }
}

6. 具体访问者2:计算总大小

java 复制代码
public class SizeVisitor implements Visitor {
    private int totalSize = 0;

    @Override
    public void visit(File file) {
        totalSize += file.getSize();
    }

    @Override
    public void visit(Folder folder) {
        // 文件夹本身不计大小,只遍历子元素
    }

    public int getTotalSize() { return totalSize; }
}

7. 客户端

java 复制代码
public class Client {
    public static void main(String[] args) {
        Folder root = new Folder("root");
        File f1 = new File("a.txt", 10);
        File f2 = new File("b.jpg", 20);
        Folder sub = new Folder("sub");
        File f3 = new File("c.md", 30);

        sub.add(f3);
        root.add(f1);
        root.add(f2);
        root.add(sub);

        // 打印
        System.out.println("=== 打印访问者 ===");
        Visitor print = new PrintVisitor();
        root.accept(print);

        // 统计大小
        System.out.println("\n=== 大小访问者 ===");
        SizeVisitor sizeVisitor = new SizeVisitor();
        root.accept(sizeVisitor);
        System.out.println("总大小:" + sizeVisitor.getTotalSize());
    }
}

输出:

复制代码
=== 打印访问者 ===
文件夹:root
文件:a.txt 大小:10
文件:b.jpg 大小:20
文件夹:sub
文件:c.md 大小:30

=== 大小访问者 ===
总大小:60

四、双分派(核心机制)

  • 第一次分派:element.accept(visitor)
  • 第二次分派:visitor.visit(this)

结果:执行的是【具体元素 + 具体访问者】的组合方法

→ 这就是访问者能精准匹配类型的原因。


五、优缺点

优点

  1. 优秀的扩展性:加新功能只需加访问者,不改元素
  2. 职责单一:元素存数据,访问者做操作
  3. 集中式操作:可一次性对整个结构做统一处理
  4. 适合组合结构 + 多种算法

缺点

  1. 元素结构不能变:增删元素类要改所有Visitor
  2. 破坏封装:Visitor需要访问元素内部数据
  3. 复杂度高,理解成本高

六、应用场景

  • 组合结构(文件树、订单树、AST)
  • 频繁新增操作,但结构稳定
  • 报表、打印、审计、校验、压缩、序列化
  • 编译器、IDE插件、代码检查

经典应用:

  • MyBatis Mapper 解析
  • Spring Event 访问式监听
  • Java NIO 文件访问
  • IDEA 代码检查/重构

七、访问者 VS 迭代器 VS 组合

  • 组合:树形结构
  • 迭代器:遍历元素
  • 访问者 :对元素做不同操作,遍历+处理一体

八、总结

  1. 访问者模式 = 稳定结构 + 无限扩展操作
  2. 核心:双分派、数据与操作分离
  3. 最适合:组合结构 + 多算法/多报表
  4. 设计模式中最难但最强大的模式之一
相关推荐
砍光二叉树2 小时前
【设计模式】行为型-状态模式
设计模式·状态模式
是糖糖啊18 小时前
Google Stitch 用 AI 将想法秒变高保真 UI,并一键导出 Figma / 代码
设计模式·产品经理·产品
Yu_Lijing1 天前
基于C++的《Head First设计模式》笔记——访问者模式
c++·笔记·设计模式
workflower1 天前
未来图景对制造系统提出全面理解、
设计模式·集成测试·软件工程·软件构建·制造·结对编程
程序员小寒1 天前
JavaScript设计模式(七):迭代器模式实现与应用
前端·javascript·设计模式·迭代器模式
hnlgzb1 天前
MVC和MVVM设计模式中对应的是什么组件?有什么对应关系?
设计模式·mvc
tobias.b2 天前
软件设计模式:核心术语·名词解释·关联对比
设计模式
hnlgzb2 天前
目前编写安卓app的话有哪几种设计模式?
android·设计模式·kotlin·android jetpack·compose
pedestrian_h2 天前
Java单例模式回顾
java·单例模式·设计模式