访问者模式(Visitor Pattern)
当你想要为一个对象的组合增加新的能力, 且封装并不重要时, 就使用访问者模式。
访问者模式是行为型设计模式的一种,其核心思想是分离数据结构与数据操作,使得操作可以独立于数据结构进行扩展。在面对 “数据结构稳定但操作多变” 的场景时,访问者模式能显著提升代码的灵活性和可维护性。
定义
访问者模式的官方定义为: 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下,定义作用于这些元素的新操作。
简单来说:假设存在一个 “对象结构”(如集合、树形结构),其中包含多个 “元素”(Element)。访问者(Visitor)可以遍历该结构,并对不同类型的元素执行自定义操作,且新增操作时无需修改元素类或对象结构。
意图
访问者模式的核心意图是解决以下问题:
- 当数据结构(元素集合)稳定不变,但需要对元素执行多种不同且易扩展的操作时,避免在元素类中堆砌大量操作方法(导致类臃肿、违反单一职责)。
- 让操作(访问者)可以独立于元素类进行扩展,新增操作只需新增访问者,无需修改现有代码(符合开闭原则)。
- 集中管理同一类操作(如所有 “统计类” 操作放在统计访问者中,所有 “报表类” 操作放在报表访问者中),提升代码可读性和可维护性。
结构
访问者模式包含 5 个核心角色,各角色职责明确,协同实现 “数据结构与操作分离”:
| 角色名称 | 核心职责 |
|---|---|
| 抽象访问者(Visitor) | 定义访问所有具体元素的接口,声明一个或多个visit(具体元素)方法(对应每种元素类型)。 |
| 具体访问者(ConcreteVisitor) | 实现抽象访问者的接口,定义对具体元素的实际操作逻辑(如统计、计算、打印等)。 |
| 抽象元素(Element) | 定义接受访问者的接口,声明accept(Visitor visitor)方法,该方法会调用访问者的visit方法。 |
| 具体元素(ConcreteElement) | 实现抽象元素的accept方法,在方法内部调用访问者的visit(当前元素)(触发双分派)。 |
| 对象结构(ObjectStructure) | 存储所有元素的集合,提供遍历元素的方法,并允许访问者访问其内部元素(如accept(Visitor))。 |
优点
- 符合开闭原则:新增操作只需新增具体访问者,无需修改元素类或对象结构。
- 集中管理操作:同一类操作(如 “统计类”“报表类”)可集中在一个访问者中,避免元素类臃肿(符合单一职责)。
- 灵活扩展操作:可快速切换不同访问者,实现对同一元素集合的不同操作(如 HR 评估、财务计算、行政登记)。
- 简化元素类:元素类只需关注自身属性和
accept方法,无需包含大量操作逻辑。
缺点
- 破坏元素封装性:访问者需获取元素的内部状态(如薪资、时薪),元素需提供
Getter或暴露私有属性,违反封装原则。 - 元素类变更困难:若新增 / 删除具体元素(如新增 “实习生”),需修改所有访问者的接口和实现(添加
visit(Intern)方法),违反开闭原则。 - 依赖关系复杂:访问者与元素之间存在强耦合(访问者需知道所有元素类型),增加代码理解和维护成本。
- 对象结构遍历依赖:对象结构需提供遍历接口,若结构复杂(如树形结构),遍历逻辑可能与访问者耦合。
类图(Mermaid)
- Visitor: 访问者,为每一个 ConcreteElement 声明一个 visit 操作
- ConcreteVisitor: 具体访问者,存储遍历过程中的累计结果
- ObjectStructure: 对象结构,可以是组合结构,或者是一个集合。

以 “公司部门访问员工” 为例(HR 部门评估绩效、财务部门计算薪资),类图如下:
+---------------------+ +---------------------+
| Visitor | | Element |
+---------------------+ +---------------------+
| + visit(FullTime):void |<--->| + accept(Visitor):void |
| + visit(PartTime):void | +---------------------+
+---------------------+ ^
^ |
| |
+---------------------+ +---------------------+
| HRDepartment | | FullTimeEmployee |
+---------------------+ +---------------------+
| + visit(FullTime):void | | - name:String |
| + visit(PartTime):void | | - salary:int |
+---------------------+ | + accept(Visitor):void |
+---------------------+
+---------------------+ ^
| FinanceDepartment | |
+---------------------+ +---------------------+
| + visit(FullTime):void | | PartTimeEmployee |
| + visit(PartTime):void | +---------------------+
+---------------------+ | - name:String |
| - hourlyWage:int |
| + accept(Visitor):void |
+---------------------+
^
|
+---------------------+
| EmployeeList | // 对象结构
+---------------------+
| - employees:List<Element> |
| + add(Element):void |
| + accept(Visitor):void |
+---------------------+
classDiagram
direction TB
%% 1. 抽象访问者(Visitor):定义访问所有具体元素的接口
class Department {
<<interface>>
+ visit(FullTimeEmployee employee) void
+ visit(PartTimeEmployee employee) void
}
%% 2. 具体访问者(ConcreteVisitor):实现抽象访问者的操作逻辑
class HRDepartment {
+ visit(FullTimeEmployee employee) void
+ visit(PartTimeEmployee employee) void
}
class FinanceDepartment {
+ visit(FullTimeEmployee employee) void
+ visit(PartTimeEmployee employee) void
}
%% 3. 抽象元素(Element):定义接受访问者的接口
class Employee {
<<interface>>
+ accept(Department department) void
+ getName() String
}
%% 4. 具体元素(ConcreteElement):实现 accept 方法,触发访问者操作
class FullTimeEmployee {
- name: String
- salary: int // 月薪
+ FullTimeEmployee(name: String, salary: int)
+ accept(Department department) void
+ getName() String
+ getSalary() int
}
class PartTimeEmployee {
- name: String
- hourlyWage: int // 时薪
+ PartTimeEmployee(name: String, hourlyWage: int)
+ accept(Department department) void
+ getName() String
+ getHourlyWage() int
}
%% 5. 对象结构(ObjectStructure):管理元素集合,提供遍历入口
class EmployeeList {
- employees: List~Employee~
+ addEmployee(employee: Employee) void
+ accept(department: Department) void
}
%% 角色间依赖关系
%% 访问者与元素:具体访问者依赖具体元素(通过 visit 方法参数)
HRDepartment ..|> Department // 具体访问者实现抽象访问者
FinanceDepartment ..|> Department
%% 元素与访问者:具体元素依赖抽象访问者(通过 accept 方法参数)
FullTimeEmployee ..|> Employee // 具体元素实现抽象元素
PartTimeEmployee ..|> Employee
%% 对象结构与元素:对象结构聚合元素(内部存储 Employee 列表)
EmployeeList "1" -- "*" Employee : 包含 >
%% 双分派核心依赖:元素调用访问者的 visit 方法,访问者操作元素属性
FullTimeEmployee ..> Department : 调用 accept >
PartTimeEmployee ..> Department : 调用 accept >
Department ..> FullTimeEmployee : 调用 visit >
Department ..> PartTimeEmployee : 调用 visit >
时序图(Mermaid)
延续 “HR 部门访问员工” 场景,时序图展示交互流程:
- 客户端(Client)创建对象结构(EmployeeList)、具体元素(FullTimeEmployee/PartTimeEmployee)和具体访问者(HRDepartment)。
- 客户端将元素添加到对象结构中。
- 客户端调用对象结构的
accept(HRDepartment)方法。 - 对象结构遍历内部元素,调用每个元素的
accept(HRDepartment)方法。 - 元素调用访问者的
visit(当前元素)方法,触发具体操作。
sequenceDiagram
participant Client as 客户端
participant EmpList as 对象结构(EmployeeList)
participant FullTime as 具体元素(FullTimeEmployee)
participant HR as 具体访问者(HRDepartment)
%% 1. 初始化对象结构
Client->>EmpList: 1. new EmployeeList()
Note right of Client: 创建存储员工的集合结构
%% 2. 初始化具体元素(全职员工)
Client->>FullTime: 2. new FullTimeEmployee(张三, 20000)
Note right of Client: 定义员工属性(姓名/薪资)
%% 3. 将元素添加到对象结构
Client->>EmpList: 3. addEmployee(FullTime)
EmpList-->>Client: 3.1 元素添加成功
Note left of EmpList: 内部维护员工列表
%% 4. 初始化具体访问者(HR部门)
Client->>HR: 4. new HRDepartment()
Note right of Client: 创建执行“绩效评估”的访问者
%% 5. 客户端触发对象结构的访问逻辑
Client->>EmpList: 5. accept(HR)
Note left of EmpList: 启动元素遍历与访问流程
%% 6. 对象结构遍历元素,调用元素的accept方法(第一次分派)
EmpList->>FullTime: 6. accept(HR)
Note right of FullTime: 元素接收访问者,准备触发具体操作
%% 7. 元素调用访问者的visit方法(第二次分派,双分派核心)
FullTime->>HR: 7. visit(this)
Note left of HR: 明确元素类型(FullTime),执行对应操作
%% 8. 访问者执行具体业务逻辑(HR评估绩效)
HR-->>FullTime: 8. 执行绩效评估(月薪≥15000→优秀)
Note right of HR: 基于员工属性计算评估结果
%% 9. 元素向对象结构返回操作结果
FullTime-->>EmpList: 9. 评估结果返回
Note left of EmpList: 接收当前元素的操作反馈
%% 10. 对象结构向客户端返回最终结果
EmpList-->>Client: 10. 所有员工评估完成
Note right of Client: 整个访问流程结束
适用环境
访问者模式仅适用于以下场景,否则会放大其缺点:
- 数据结构稳定,操作多变:如集合、树形结构(如 XML/JSON 节点),且需频繁新增操作(如报表生成、数据校验、统计分析)。
- 需集中管理多组操作:如同一批数据需生成 “Excel 报表”“PDF 报表”“CSV 报表”,可对应三个访问者。
- 元素类型明确且固定:如元素仅为 “全职员工”“兼职员工”,不会频繁新增类型(否则需修改所有访问者)。
- 需遍历复杂对象结构:如遍历树形结构的每个节点,执行不同操作(如语法树分析、文件目录扫描)。
模式分析
访问者模式的核心是双分派机制和数据结构与操作的分离,需重点理解以下两点:
1. 双分派机制(Double Dispatch)
“分派” 指确定调用哪个方法的过程。访问者模式通过两次分派实现 “元素类型” 和 “访问者类型” 的动态绑定:
- 第一次分派:客户端调用对象结构的
accept(visitor),对象结构遍历元素,调用element.accept(visitor)(此时element是具体元素,visitor是具体访问者)。 - 第二次分派:具体元素的
accept方法中,调用visitor.visit(this)(this明确指向具体元素,触发访问者对该元素的特定操作)。
例如:fullTimeEmployee.accept(hrDepartment) → 内部调用hrDepartment.visit(fullTimeEmployee),最终执行 HR 部门对全职员工的绩效评估逻辑。
2. 分离的核心价值
- 数据结构稳定:元素类(如
FullTimeEmployee)只需定义accept方法,无需修改即可支持新操作(新增访问者即可)。 - 操作独立扩展:新增操作(如 “行政部门登记员工信息”)只需新增
AdminDepartment类(实现Visitor),无需修改元素类或对象结构。
模式扩展
访问者模式可结合其他模式进行扩展,满足更复杂需求:
1. 组合访问者(Composite Visitor)
结合组合模式,将多个访问者组合为一个 “复合访问者”,批量执行操作。例如:同时执行 HR 评估和财务计算,无需客户端分别调用。
// 复合访问者
public class CompositeDepartment implements Department {
private List<Department> departments = new ArrayList<>();
public void addDepartment(Department department) {
departments.add(department);
}
@Override
public void visit(FullTimeEmployee employee) {
for (Department dept : departments) {
dept.visit(employee);
}
}
@Override
public void visit(PartTimeEmployee employee) {
for (Department dept : departments) {
dept.visit(employee);
}
}
}
// 客户端使用
CompositeDepartment composite = new CompositeDepartment();
composite.addDepartment(new HRDepartment());
composite.addDepartment(new FinanceDepartment());
employeeList.accept(composite); // 一次调用,执行两个部门的操作
2. 带状态的访问者(Stateful Visitor)
访问者内部维护状态,遍历元素时累积状态(如统计总薪资、总人数)。例如:财务部门统计所有员工的薪资总和。
public class FinanceStatisticsDepartment implements Department {
private int totalSalary = 0; // 状态:总薪资
@Override
public void visit(FullTimeEmployee employee) {
totalSalary += employee.getSalary();
}
@Override
public void visit(PartTimeEmployee employee) {
totalSalary += employee.getHourlyWage() * 40; // 周薪折算为月薪(假设4周)
}
// 获取统计结果
public int getTotalSalary() {
return totalSalary;
}
}
// 客户端使用
FinanceStatisticsDepartment stats = new FinanceStatisticsDepartment();
employeeList.accept(stats);
System.out.println("所有员工总薪资:" + stats.getTotalSalary() + "元");
3. 过滤访问者(Filtered Visitor)
结合过滤器模式,仅对符合条件的元素执行操作。例如:HR 部门仅评估月薪≥20000 的全职员工。
public class FilteredHRDepartment implements Department {
@Override
public void visit(FullTimeEmployee employee) {
if (employee.getSalary() >= 20000) { // 过滤条件
System.out.println("HR部门重点评估【高薪全职员工】" + employee.getName());
}
}
@Override
public void visit(PartTimeEmployee employee) {
// 不处理兼职员工
}
}
模式应用
访问者模式在框架和业务场景中应用广泛,典型案例包括:
- Java 语言:
javax.lang.model.element.ElementVisitor:注解处理器中,访问 Java 语法元素(类、方法、字段)的接口。java.nio.file.FileVisitor:文件系统遍历中,访问文件 / 目录的接口(如Files.walkFileTree)。
- 编译器 / 解析器:
- 语法分析阶段,访问抽象语法树(AST)的每个节点,执行语义检查、代码生成等操作(如 Java 编译器的
com.sun.tools.javac.tree.TreeScanner)。
- 语法分析阶段,访问抽象语法树(AST)的每个节点,执行语义检查、代码生成等操作(如 Java 编译器的
- 报表系统:
- 同一批业务数据(如订单、用户)需生成 “销售额报表”“用户活跃度报表”“退款率报表”,对应三个访问者。
- 数据导出工具:
- 数据库表数据需导出为 Excel、CSV、JSON 格式,每种格式对应一个访问者,遍历表结构并生成文件。
Android 中的应用
Android 框架和开发场景中,访问者模式主要用于 “结构稳定、操作多变” 的场景:
1. 注解处理器(APT)
Android 开发中,ButterKnife、Dagger2 等框架的注解处理器,核心依赖javax.lang.model.element.ElementVisitor(访问者模式):
- 元素:Java 语法元素(
TypeElement- 类、ExecutableElement- 方法、VariableElement- 字段)。 - 访问者:框架自定义的访问者(如 ButterKnife 的
BindViewProcessor),遍历元素并生成绑定代码。
2. 资源解析
Android 的AssetManager或Resources在解析资源(如 XML 布局、字符串)时,会遍历资源结构,使用访问者模式执行解析操作(如解析布局中的 View 节点)。
3. RecyclerView 遍历(自定义场景)
若需对 RecyclerView 的 Item 执行多种操作(如 “标记已读”“批量删除”“统计选中数”),可定义ItemVisitor接口,每个操作对应一个具体访问者,避免在 Adapter 中堆砌逻辑:
// 抽象访问者
public interface ItemVisitor {
void visit(MessageItem item); // 访问“消息Item”
}
// 具体访问者:标记已读
public class MarkAsReadVisitor implements ItemVisitor {
@Override
public void visit(MessageItem item) {
item.setRead(true);
}
}
// Item元素
public class MessageItem {
private boolean isRead;
// ... 其他属性
public void accept(ItemVisitor visitor) {
visitor.visit(this);
}
public void setRead(boolean read) {
isRead = read;
}
}
4. 数据库操作(Room)
Room 框架在处理实体类(@Entity)时,会使用访问者模式解析实体的字段、主键、索引等信息,生成数据库操作代码(如Dao实现)。
代码实现(Java 版)
以 “公司部门访问员工” 为例,完整实现访问者模式:
1. 抽象访问者(Visitor)
定义访问所有具体元素的接口:
// 抽象访问者:部门
public interface Department {
// 访问全职员工
void visit(FullTimeEmployee employee);
// 访问兼职员工
void visit(PartTimeEmployee employee);
}
2. 具体访问者(ConcreteVisitor)
实现具体操作(HR 评估绩效、财务计算薪资):
// 具体访问者1:HR部门(评估绩效)
public class HRDepartment implements Department {
@Override
public void visit(FullTimeEmployee employee) {
System.out.println("HR部门评估【全职员工】" + employee.getName() + ":绩效优秀(月薪≥15000)");
}
@Override
public void visit(PartTimeEmployee employee) {
System.out.println("HR部门评估【兼职员工】" + employee.getName() + ":绩效合格(时薪≥50)");
}
}
// 具体访问者2:财务部门(计算薪资)
public class FinanceDepartment implements Department {
@Override
public void visit(FullTimeEmployee employee) {
int salary = employee.getSalary();
System.out.println("财务部门计算【全职员工】" + employee.getName() + ":月薪=" + salary + "元");
}
@Override
public void visit(PartTimeEmployee employee) {
int weeklyWage = employee.getHourlyWage() * 40; // 假设每周工作40小时
System.out.println("财务部门计算【兼职员工】" + employee.getName() + ":周薪=" + weeklyWage + "元");
}
}
3. 抽象元素(Element)
定义接受访问者的接口:
// 抽象元素:员工
public interface Employee {
// 接受访问者访问
void accept(Department department);
// 获取员工姓名(供访问者使用)
String getName();
}
4. 具体元素(ConcreteElement)
实现accept方法,触发访问者操作:
// 具体元素1:全职员工
public class FullTimeEmployee implements Employee {
private String name;
private int salary; // 月薪
public FullTimeEmployee(String name, int salary) {
this.name = name;
this.salary = salary;
}
@Override
public void accept(Department department) {
// 调用访问者的visit方法(双分派的关键)
department.visit(this);
}
// Getter(供访问者获取内部状态)
@Override
public String getName() {
return name;
}
public int getSalary() {
return salary;
}
}
// 具体元素2:兼职员工
public class PartTimeEmployee implements Employee {
private String name;
private int hourlyWage; // 时薪
public PartTimeEmployee(String name, int hourlyWage) {
this.name = name;
this.hourlyWage = hourlyWage;
}
@Override
public void accept(Department department) {
department.visit(this);
}
// Getter
@Override
public String getName() {
return name;
}
public int getHourlyWage() {
return hourlyWage;
}
}
5. 对象结构(ObjectStructure)
管理元素集合,提供访问入口:
// 对象结构:员工列表
import java.util.ArrayList;
import java.util.List;
public class EmployeeList {
// 存储所有员工
private List<Employee> employees = new ArrayList<>();
// 添加员工
public void addEmployee(Employee employee) {
employees.add(employee);
}
// 接受访问者访问(遍历所有员工)
public void accept(Department department) {
for (Employee employee : employees) {
employee.accept(department);
}
}
}
6. 客户端(Client)
测试代码,模拟部门访问员工:
public class Client {
public static void main(String[] args) {
// 1. 创建对象结构和元素
EmployeeList employeeList = new EmployeeList();
employeeList.addEmployee(new FullTimeEmployee("张三", 20000));
employeeList.addEmployee(new PartTimeEmployee("李四", 60));
employeeList.addEmployee(new FullTimeEmployee("王五", 15000));
// 2. HR部门访问员工(评估绩效)
System.out.println("=== HR部门访问员工 ===");
Department hr = new HRDepartment();
employeeList.accept(hr);
// 3. 财务部门访问员工(计算薪资)
System.out.println("\n=== 财务部门访问员工 ===");
Department finance = new FinanceDepartment();
employeeList.accept(finance);
}
}
7. 运行结果
=== HR部门访问员工 ===
HR部门评估【全职员工】张三:绩效优秀(月薪≥15000)
HR部门评估【兼职员工】李四:绩效合格(时薪≥50)
HR部门评估【全职员工】王五:绩效优秀(月薪≥15000)
=== 财务部门访问员工 ===
财务部门计算【全职员工】张三:月薪=20000元
财务部门计算【兼职员工】李四:周薪=2400元
财务部门计算【全职员工】王五:月薪=15000元
实现2
public interface Element {
void accept(Visitor visitor);
}
class CustomerGroup {
private List<Customer> customers = new ArrayList<>();
void accept(Visitor visitor) {
for (Customer customer : customers) {
customer.accept(visitor);
}
}
void addCustomer(Customer customer) {
customers.add(customer);
}
}
public class Customer implements Element {
private String name;
private List<Order> orders = new ArrayList<>();
Customer(String name) {
this.name = name;
}
String getName() {
return name;
}
void addOrder(Order order) {
orders.add(order);
}
public void accept(Visitor visitor) {
visitor.visit(this);
for (Order order : orders) {
order.accept(visitor);
}
}
}
public class Order implements Element {
private String name;
private List<Item> items = new ArrayList();
Order(String name) {
this.name = name;
}
Order(String name, String itemName) {
this.name = name;
this.addItem(new Item(itemName));
}
String getName() {
return name;
}
void addItem(Item item) {
items.add(item);
}
public void accept(Visitor visitor) {
visitor.visit(this);
for (Item item : items) {
item.accept(visitor);
}
}
}
public class Item implements Element {
private String name;
Item(String name) {
this.name = name;
}
String getName() {
return name;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public interface Visitor {
void visit(Customer customer);
void visit(Order order);
void visit(Item item);
}
public class GeneralReport implements Visitor {
private int customersNo;
private int ordersNo;
private int itemsNo;
public void visit(Customer customer) {
System.out.println(customer.getName());
customersNo++;
}
public void visit(Order order) {
System.out.println(order.getName());
ordersNo++;
}
public void visit(Item item) {
System.out.println(item.getName());
itemsNo++;
}
public void displayResults() {
System.out.println("Number of customers: " + customersNo);
System.out.println("Number of orders: " + ordersNo);
System.out.println("Number of items: " + itemsNo);
}
}
public class Client {
public static void main(String[] args) {
Customer customer1 = new Customer("customer1");
customer1.addOrder(new Order("order1", "item1"));
customer1.addOrder(new Order("order2", "item1"));
customer1.addOrder(new Order("order3", "item1"));
Order order = new Order("order_a");
order.addItem(new Item("item_a1"));
order.addItem(new Item("item_a2"));
order.addItem(new Item("item_a3"));
Customer customer2 = new Customer("customer2");
customer2.addOrder(order);
CustomerGroup customers = new CustomerGroup();
customers.addCustomer(customer1);
customers.addCustomer(customer2);
GeneralReport visitor = new GeneralReport();
customers.accept(visitor);
visitor.displayResults();
}
}
customer1
order1
item1
order2
item1
order3
item1
customer2
order_a
item_a1
item_a2
item_a3
Number of customers: 2
Number of orders: 4
Number of items: 6
总结
访问者模式是一把 “双刃剑”,其核心价值在于分离数据结构与操作,但依赖于 “数据结构稳定” 的前提:
- 核心优势:
- 操作可独立扩展,符合开闭原则;
- 集中管理同类操作,简化元素类;
- 支持复杂对象结构的遍历与多维度处理。
- 核心局限:
- 破坏元素封装性,依赖
Getter暴露状态; - 元素类型变更困难,违反开闭原则;
- 依赖关系复杂,增加代码理解成本。
- 破坏元素封装性,依赖
- 使用建议:
- 仅当数据结构稳定、操作多变时使用(如编译器、报表系统);
- 避免在元素类型频繁变更的场景中使用(如业务迭代快的核心模块);
- 可结合组合模式、过滤器模式扩展功能,平衡灵活性与可维护性。
访问者模式不是 “银弹”,需根据业务场景权衡其优缺点,避免为了 “用模式而用模式”。