访问者模式

访问者模式:分离算法与数据结构的设计智慧

一、访问者模式的核心定义

访问者模式(Visitor Pattern) 是一种行为型设计模式,其核心思想是 分离数据结构与作用于数据结构上的操作。当需要对一个复杂对象结构中的所有元素执行多种不同操作,且操作频繁变化时,访问者模式可让操作独立于元素类而变化,避免修改元素类代码,同时简化新操作的添加。

简单来说:访问者模式就像 “上门服务”—— 数据结构(如对象集合)是 “住户”,访问者(操作逻辑)是 “服务人员”,服务人员可根据住户类型提供不同服务,且新增服务无需改动住户本身。

二、访问者模式的核心结构

访问者模式包含 5 个关键角色,各角色职责明确、协同工作:

角色 核心职责
抽象访问者(Visitor) 定义访问所有具体元素的接口,声明与元素类对应的 visit() 方法(一个元素对应一个方法)。
具体访问者(ConcreteVisitor) 实现抽象访问者的接口,封装对具体元素的特定操作逻辑(如统计、打印、计算等)。
抽象元素(Element) 定义接收访问者的接口,声明 accept(Visitor visitor) 方法,该方法会将自身传入访问者。
具体元素(ConcreteElement) 实现抽象元素的 accept() 方法,调用访问者对应的 visit() 方法,完成 “双重分派”。
对象结构(ObjectStructure) 存储元素集合,提供遍历元素的方法,可主动邀请访问者访问所有元素(可选角色)。

核心逻辑:双重分派

访问者模式的关键是 “双重分派” 机制,即:

  1. 元素的 accept() 方法接收访问者,调用访问者的 visit(this)(第一重分派:确定元素类型);

  2. 访问者的 visit() 方法根据传入的元素实例,执行对应操作(第二重分派:确定操作类型)。

这种机制确保了 “操作与元素” 的动态绑定,无需修改元素类即可扩展新操作。

三、访问者模式的代码实现

以 “文档编辑器” 为例:文档包含文本(Text)、图片(Image)、表格(Table)三种元素,需实现 “统计元素数量” 和 “导出为 PDF” 两种操作,用访问者模式分离元素与操作。

1. 抽象元素(Element)

// 抽象元素:定义接收访问者的方法

public interface Element {

   void accept(Visitor visitor);

}

2. 具体元素(ConcreteElement)

// 具体元素:文本

public class Text implements Element {

   private String content;

   public Text(String content) {

       this.content = content;

   }

   public String getContent() {

       return content;

   }

   @Override

   public void accept(Visitor visitor) {

       // 调用访问者对应自身的方法

       visitor.visit(this);

   }

}

// 具体元素:图片

public class Image implements Element {

   private String name;

   private int size; // 图片大小(KB)

   public Image(String name, int size) {

       this.name = name;

       this.size = size;

   }

   public String getName() {

       return name;

   }

   public int getSize() {

       return size;

   }

   @Override

   public void accept(Visitor visitor) {

       visitor.visit(this);

   }

}

// 具体元素:表格

public class Table implements Element {

   private int row;

   private int col;

   public Table(int row, int col) {

       this.row = row;

       this.col = col;

   }

   public int getRow() {

       return row;

   }

   public int getCol() {

       return col;

   }

   @Override

   public void accept(Visitor visitor) {

       visitor.visit(this);

   }

}

3. 抽象访问者(Visitor)

// 抽象访问者:声明访问所有具体元素的方法

public interface Visitor {

   void visit(Text text);       // 访问文本元素

   void visit(Image image);     // 访问图片元素

   void visit(Table table);     // 访问表格元素

}

4. 具体访问者(ConcreteVisitor)

// 具体访问者1:统计元素数量

public class CountVisitor implements Visitor {

   private int textCount = 0;

   private int imageCount = 0;

   private int tableCount = 0;

   @Override

   public void visit(Text text) {

       textCount++;

       System.out.println("统计文本:" + text.getContent().length() + "...");

   }

   @Override

   public void visit(Image image) {

       imageCount++;

       System.out.println("统计图片:" + image.getName() + "(大小:" + image.getSize() + "KB)");

   }

   @Override

   public void visit(Table table) {

       tableCount++;

       System.out.println("统计表格:" + table.getRow() + "行" + table.getCol() + "列");

   }

   // 输出统计结果

   public void printResult() {

       System.out.println("\n=== 元素统计结果 ===");

       System.out.println("文本数量:" + textCount);

       System.out.println("图片数量:" + imageCount);

       System.out.println("表格数量:" + tableCount);

       System.out.println("总元素数:" + (textCount + imageCount + tableCount));

   }

}

// 具体访问者2:导出为PDF

public class ExportPdfVisitor implements Visitor {

   @Override

   public void visit(Text text) {

       System.out.println("导出文本到PDF:" + text.getContent());

   }

   @Override

   public void visit(Image image) {

       System.out.println("导出图片到PDF:" + image.getName() + "(压缩至" + image.getSize() * 0.8 + "KB)");

   }

   @Override

   public void visit(Table table) {

       System.out.println("导出表格到PDF:" + table.getRow() + "行" + table.getCol() + "列(格式化布局)");

   }

}

5. 对象结构(ObjectStructure)

// 对象结构:管理元素集合,提供遍历功能

import java.util.ArrayList;

import java.util.List;

public class Document {

   private List<Element> elements = new ArrayList<>();

   // 添加元素

   public void addElement(Element element) {

       elements.add(element);

   }

   // 移除元素

   public void removeElement(Element element) {

       elements.remove(element);

   }

   // 接受访问者,遍历所有元素并让访问者访问

   public void accept(Visitor visitor) {

       for (Element element : elements) {

           element.accept(visitor);

       }

   }

}

6. 客户端调用

public class Client {

   public static void main(String[] args) {

       // 1. 创建文档(对象结构)并添加元素

       Document document = new Document();

       document.addElement(new Text("访问者模式详解"));

       document.addElement(new Image("设计模式示意图", 200));

       document.addElement(new Table(5, 3));

       document.addElement(new Text("双重分派机制"));

       document.addElement(new Image("代码示例截图", 150));

       // 2. 统计元素数量(访问者1)

       CountVisitor countVisitor = new CountVisitor();

       document.accept(countVisitor);

       countVisitor.printResult();

       // 3. 导出为PDF(访问者2)

       System.out.println("\n=== 开始导出PDF ===");

       ExportPdfVisitor exportVisitor = new ExportPdfVisitor();

       document.accept(exportVisitor);

   }

}

输出结果

统计文本:访问者模式详解...

统计图片:设计模式示意图(大小:200KB)

统计表格:5行3列

统计文本:双重分派机制...

统计图片:代码示例截图(大小:150KB)

\=== 元素统计结果 ===

文本数量:2

图片数量:2

表格数量:1

总元素数:5

\=== 开始导出PDF ===

导出文本到PDF:访问者模式详解

导出图片到PDF:设计模式示意图(压缩至160.0KB)

导出表格到PDF:5行3列(格式化布局)

导出文本到PDF:双重分派机制

导出图片到PDF:代码示例截图(压缩至120.0KB)

四、访问者模式的适用场景

当满足以下条件时,适合使用访问者模式:

  1. 数据结构稳定,但操作频繁变化:如示例中的 “文档元素”(文本、图片、表格)很少新增,但 “统计、导出、校验” 等操作可能不断扩展;

  2. 需要对复杂对象结构中的所有元素执行统一操作:如遍历集合中的不同类型对象,执行相同逻辑的变体(如统计、格式化);

  3. 避免在元素类中添加过多操作方法:若操作逻辑分散在各个元素类中,会导致类膨胀、耦合度高,访问者模式可将操作集中管理。

五、访问者模式的优势与缺点

优势

  1. 符合开闭原则:新增操作只需添加新的具体访问者,无需修改元素类和对象结构,扩展灵活;

  2. 分离关注点:将数据结构与操作逻辑分离,元素类只关注自身数据,操作逻辑集中在访问者中,代码结构清晰;

  3. 便于批量处理:通过对象结构的遍历功能,可快速对所有元素执行同一访问者的操作,提高代码复用性。

缺点

  1. 元素类变更困难:若新增或修改元素类(如文档新增 “视频” 元素),需修改抽象访问者和所有具体访问者的接口,违反开闭原则;

  2. 破坏封装性:访问者需要访问元素的内部状态(如图片的大小、表格的行列数),可能会暴露元素的私有属性,降低封装性;

  3. 复杂度较高:双重分派机制和多角色协作增加了代码理解成本,适合复杂场景,简单场景可能显得冗余。

六、访问者模式的实际应用场景

  1. 框架源码:Java 的 java.nio.file.FileVisitor(文件访问器)、Spring 的 BeanDefinitionVisitor(Bean 定义遍历);

  2. 数据处理工具:Excel 导出工具(不同单元格类型(文本、数字、公式)的导出逻辑)、JSON 解析器(不同数据类型的解析逻辑);

  3. 业务系统:电商订单结算(不同商品类型的折扣计算)、报表生成(不同数据维度的统计与展示)。

posted @ 2025-12-12 02:25  圣祖帝皇  阅读(19)  评论(0)    收藏  举报