一、定义
访问者模式(Visitor Pattern)是一种行为型设计模式,它将作用于某对象结构的操作封装到独立的类中,使得在不改变对象结构的前提下添加新的操作变得更加容易。
本质:让操作和结构解耦,通过“双分派”机制在访问者中实现针对不同类型元素的处理逻辑。
二、适用场景
- 有一个对象结构(如 AST、表单、订单、规则树等),包含多种类型元素;
- 想对结构中不同类型的元素执行不同操作(如校验、导出、计算、转换等);
- 希望频繁添加操作,而不是修改元素类本身;
- 元素结构比较稳定,但操作种类经常变化。
三、核心角色
角色 |
说明 |
|
抽象访问者,声明访问每个元素的方法 |
|
具体访问者,实现访问逻辑(如计算、打印、校验等) |
|
抽象元素,定义 |
|
具体元素,实现 accept 并将自身传递给访问者的对应方法 |
|
元素集合类,负责管理元素并接受访问者 |
四、业务背景示例:保险理赔系统规则引擎
在保险理赔系统中,不同的保单类型(医疗险、意外险、财产险)有不同的计算规则,我们希望可以在不修改保单类型类的前提下:
- 增加理赔金额计算逻辑
- 增加打印规则报告
- 增加风控规则验证
使用访问者模式非常适合!
五、代码实现
1. 抽象元素接口(Element 角色)
// 所有保单的抽象接口,定义接受访问者的入口
public interface PolicyElement {
void accept(PolicyVisitor visitor);
}
2. 具体元素类(ConcreteElement 角色)
// 医疗保险保单
public class MedicalPolicy implements PolicyElement {
private double baseAmount;
public MedicalPolicy(double baseAmount) {
this.baseAmount = baseAmount;
}
public double getBaseAmount() {
return baseAmount;
}
@Override
public void accept(PolicyVisitor visitor) {
visitor.visitMedical(this);
}
}
// 意外保险保单
public class AccidentPolicy implements PolicyElement {
private int accidentLevel;
public AccidentPolicy(int accidentLevel) {
this.accidentLevel = accidentLevel;
}
public int getAccidentLevel() {
return accidentLevel;
}
@Override
public void accept(PolicyVisitor visitor) {
visitor.visitAccident(this);
}
}
// 财产保险保单
public class PropertyPolicy implements PolicyElement {
private double damagePercent;
public PropertyPolicy(double damagePercent) {
this.damagePercent = damagePercent;
}
public double getDamagePercent() {
return damagePercent;
}
@Override
public void accept(PolicyVisitor visitor) {
visitor.visitProperty(this);
}
}
3. 抽象访问者接口(Visitor 角色)
public interface PolicyVisitor {
void visitMedical(MedicalPolicy policy);
void visitAccident(AccidentPolicy policy);
void visitProperty(PropertyPolicy policy);
}
4. 具体访问者:理赔金额计算(ConcreteVisitor 角色)
// 实现理赔金额的计算规则
public class ClaimAmountCalculator implements PolicyVisitor {
private double totalAmount = 0;
public double getTotalAmount() {
return totalAmount;
}
@Override
public void visitMedical(MedicalPolicy policy) {
double amount = policy.getBaseAmount() * 0.8; // 医疗赔80%
System.out.println("[医疗险] 理赔金额: " + amount);
totalAmount += amount;
}
@Override
public void visitAccident(AccidentPolicy policy) {
double amount = 1000 * policy.getAccidentLevel(); // 每等级赔1000元
System.out.println("[意外险] 理赔金额: " + amount);
totalAmount += amount;
}
@Override
public void visitProperty(PropertyPolicy policy) {
double amount = 50000 * policy.getDamagePercent(); // 最高赔5万,按比例
System.out.println("[财产险] 理赔金额: " + amount);
totalAmount += amount;
}
}
5. 对象结构类(ObjectStructure 角色)
// 保单集合,统一接受访问者
public class PolicyBook {
private List<PolicyElement> policies = new ArrayList<>();
public void addPolicy(PolicyElement policy) {
policies.add(policy);
}
public void accept(PolicyVisitor visitor) {
for (PolicyElement policy : policies) {
policy.accept(visitor);
}
}
}
6. 客户端调用(Client)
public class VisitorDemo {
public static void main(String[] args) {
// 构建保单列表
PolicyBook policyBook = new PolicyBook();
policyBook.addPolicy(new MedicalPolicy(10000));
policyBook.addPolicy(new AccidentPolicy(3));
policyBook.addPolicy(new PropertyPolicy(0.6));
// 理赔金额访问者
ClaimAmountCalculator calculator = new ClaimAmountCalculator();
policyBook.accept(calculator);
System.out.println("总理赔金额:" + calculator.getTotalAmount());
}
}
六、类与角色对照表
类名 |
模式角色 |
说明 |
|
Visitor |
抽象访问者接口 |
|
ConcreteVisitor |
理赔金额计算访问者 |
|
Element |
抽象保单元素接口 |
|
ConcreteElement |
医疗保险 |
|
ConcreteElement |
意外保险 |
|
ConcreteElement |
财产保险 |
|
ObjectStructure |
保单集合结构 |
|
Client |
客户端,执行访问者逻辑 |
七、优缺点分析(Pros & Cons)
✅ 优点
优点 |
说明 |
扩展性强 |
新增操作只需添加访问者类,不改动元素类 |
职责分离清晰 |
操作封装在访问者中,元素关注数据本身 |
支持双分派 |
根据访问者和元素的类型决定最终方法调用,避免大量 if/instanceof 判断 |
❌ 缺点
缺点 |
说明 |
元素类变更影响大 |
添加新元素,需要修改所有访问者类 |
破坏封装性 |
访问者可能访问元素内部状态,破坏其封装性 |
维护成本高 |
访问者与元素类之间强耦合,每增加一种结构或行为都要成对修改 |
八、真实业务使用场景
场景 |
应用说明 |
报表系统 |
多种报表对结构相同的数据生成不同格式(Excel/PDF/HTML) |
税务计算 |
针对不同类型产品或用户计算不同税率 |
保险理赔规则 |
本文示例:针对不同保单类型应用不同计算逻辑 |
编译器语法树遍历 |
不同的语法节点交给不同访问者生成中间代码、语义分析等 |
产品导出 |
电商商品导出多种格式(JSON、XML、YAML) |
九、总结(Summary)
访问者模式通过“操作抽离”的方式,使得在不修改原始对象结构的情况下就可以增加新行为。它非常适用于结构稳定、行为频繁变化的系统,如规则引擎、报表系统、导出工具等。