【从零入门23种设计模式24】行为型之访问者模式
一、访问者模式核心定义
访问者模式是行为型设计模式的一种,核心目的是:
将数据结构与对数据的操作分离,使得操作可以独立于数据结构变化;定义一个作用于某对象结构中各元素的操作,而无需改变各元素的类。
简单来说:把对不同类型对象的操作(如计算、校验、导出)封装成独立的 “访问者” 类,数据对象接受访问者的访问并调用对应操作,实现 “数据不动,操作动”。
核心解决的问题
- 解耦数据结构与操作:数据对象(如订单、商品、用户)的结构稳定,但对数据的操作(如统计、导出、校验)频繁变化时,无需修改数据类;
- 复用操作逻辑:同一套操作(如导出 Excel)可作用于不同类型的数据对象;
- 集中管理同类操作:所有数据对象的 “导出” 操作集中在
ExportVisitor中,而非分散在各个数据类中; - 支持多态操作:不同类型的数据对象对同一访问者会执行不同的操作(如订单导出、商品导出逻辑不同);
- 符合开闭原则:新增操作只需新增访问者类,无需修改数据对象类。
生活类比
- 场景 1:商场巡检
- 数据结构:商场中的不同元素(店铺、仓库、办公室);
- 访问者:不同的巡检人员(安全巡检员、卫生巡检员、消防巡检员);
- 核心:每个巡检人员(访问者)对不同场所(数据对象)执行不同的巡检操作,场所只需接受访问并配合巡检,无需修改自身逻辑。
- 场景 2:财务审计
- 数据结构:公司的不同资产(固定资产、流动资产、无形资产);
- 访问者:审计人员(税务审计、财务审计、合规审计);
- 核心:审计人员(访问者)对不同资产(数据对象)执行审计操作,资产类无需修改,新增审计类型只需新增访问者。
- 场景 3:电商数据统计
- 数据结构:订单、商品、用户;
- 访问者:统计访问者(销售额统计、用户活跃度统计、商品销量统计);
- 核心:统计访问者遍历所有数据对象,执行对应统计操作,新增统计维度只需新增访问者。
标准角色
| 角色 | 职责 | 类比(商场巡检场景) | 代码定位 |
|---|---|---|---|
| 抽象元素(Element) | 定义接受访问者的接口(accept(Visitor visitor)),所有数据对象实现该接口 | 场所接口(Place):accept(Inspector visitor) | 接口 / 抽象类 |
| 具体元素(ConcreteElement) | 实现抽象元素接口,调用访问者的对应方法(如visitor.visitShop(this)) | 店铺(Shop)、仓库(Warehouse) | 数据对象类 |
| 抽象访问者(Visitor) | 定义对所有具体元素的访问方法(visitShop(Shop shop)、visitWarehouse(Warehouse warehouse)) | 巡检员接口(Inspector):checkShop()、checkWarehouse() | 接口 / 抽象类 |
| 具体访问者(ConcreteVisitor) | 实现抽象访问者接口,封装对不同元素的具体操作逻辑 | 安全巡检员(SafetyInspector)、卫生巡检员(CleanlinessInspector) | 操作实现类 |
| 对象结构(ObjectStructure) | 管理元素集合,提供遍历元素的方法,供访问者遍历所有元素执行操作 | 商场(Mall):管理所有场所,提供遍历方法 | 集合管理类 |
核心 UML 类图

二、电商数据统计
以 “电商数据统计” 为例,实现访问者模式的核心逻辑 —— 数据对象(订单、商品、用户)结构稳定,统计操作(销售额、销量、活跃度)封装为访问者,新增统计维度只需新增访问者,无需修改数据类。
1. 步骤 1:定义抽象元素(数据对象接口)
/** * 抽象元素:电商数据对象接口(定义接受访问者的方法) */ public interface EcommerceElement { /** * 接受访问者访问 */ void accept(EcommerceVisitor visitor); } 2. 步骤 2:实现具体元素(订单 / 商品 / 用户)
/** * 具体元素1:订单 */ public class OrderElement implements EcommerceElement { private String orderId; // 订单ID private double amount; // 订单金额 private int productCount; // 商品数量 public OrderElement(String orderId, double amount, int productCount) { this.orderId = orderId; this.amount = amount; this.productCount = productCount; } // 核心:接受访问者,调用访问者的订单访问方法 @Override public void accept(EcommerceVisitor visitor) { visitor.visitOrder(this); } // Getter public String getOrderId() { return orderId; } public double getAmount() { return amount; } public int getProductCount() { return productCount; } } /** * 具体元素2:商品 */ public class ProductElement implements EcommerceElement { private String productId; // 商品ID private String productName; // 商品名称 private int salesCount; // 销量 public ProductElement(String productId, String productName, int salesCount) { this.productId = productId; this.productName = productName; this.salesCount = salesCount; } @Override public void accept(EcommerceVisitor visitor) { visitor.visitProduct(this); } // Getter public String getProductId() { return productId; } public String getProductName() { return productName; } public int getSalesCount() { return salesCount; } } /** * 具体元素3:用户 */ public class UserElement implements Ecomm