访问者模式(Visitor)
意图
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
UML 图
优点
- 开闭原则:容易添加新的访问者操作,无需修改元素类
- 单一职责原则:将相关行为集中到一个访问者对象中
- 灵活性:可以在运行时选择不同的访问者执行不同的操作
- 数据分离:访问者可以收集复杂的对象结构信息
- 扩展性好:新增访问者不会影响现有系统结构
缺点
- 元素接口变更困难:每增加一个新的元素类,都要在访问者接口中添加一个新的visit操作
- 破坏封装性:访问者通常需要访问元素的内部状态,可能破坏封装性
- 依赖具体类:访问者依赖具体元素类,而不是抽象接口
- 对象结构变化困难:如果对象结构经常变化,会导致需要修改所有访问者
代码示例
以人类和机器人的诊断维护系统为例:
1. 访问者接口 (Visitor)
// 访问者接口
public interface Visitor {void visitHuman(Human human);void visitRobot(Robot robot);String getReport();
}
2. 具体访问者类 (Concrete Visitors)
// 健康检查访问者
public class HealthCheckVisitor implements Visitor {private StringBuilder report = new StringBuilder();@Overridepublic void visitHuman(Human human) {report.append("=== 人类健康检查报告 ===\n");report.append("姓名: ").append(human.getName()).append("\n");report.append("年龄: ").append(human.getAge()).append("岁\n");report.append("心率: ").append(human.getHeartRate()).append("bpm\n");report.append("体温: ").append(human.getBodyTemperature()).append("°C\n");String status = human.getHeartRate() > 100 ? "⚠️ 心率过高" : human.getBodyTemperature() > 37.5 ? "⚠️ 发烧" : "✅ 健康";report.append("状态: ").append(status).append("\n\n");}@Overridepublic void visitRobot(Robot robot) {report.append("=== 机器人诊断报告 ===\n");report.append("型号: ").append(robot.getModel()).append("\n");report.append("序列号: ").append(robot.getSerialNumber()).append("\n");report.append("电池电量: ").append(robot.getBatteryLevel()).append("%\n");report.append("运行时间: ").append(robot.getOperatingHours()).append("小时\n");String status = robot.getBatteryLevel() < 20 ? "⚠️ 电量不足" : robot.getOperatingHours() > 10000 ? "⚠️ 需要维护" : "✅ 正常";report.append("状态: ").append(status).append("\n\n");}@Overridepublic String getReport() {return report.toString();}
}// 维护访问者
public class MaintenanceVisitor implements Visitor {private StringBuilder report = new StringBuilder();private double totalCost = 0;@Overridepublic void visitHuman(Human human) {report.append("=== 人类保养建议 ===\n");report.append("姓名: ").append(human.getName()).append("\n");if (human.getAge() > 40) {report.append("建议: 年度体检\n");totalCost += 500;}if (human.getHeartRate() > 90) {report.append("建议: 心血管检查\n");totalCost += 300;}report.append("预估费用: ¥").append(totalCost).append("\n\n");}@Overridepublic void visitRobot(Robot robot) {report.append("=== 机器人维护方案 ===\n");report.append("型号: ").append(robot.getModel()).append("\n");if (robot.getBatteryLevel() < 30) {report.append("建议: 更换电池\n");totalCost += 800;}if (robot.getOperatingHours() > 8000) {report.append("建议: 全面检修\n");totalCost += 2000;}if (robot.getOperatingHours() > 10000) {report.append("建议: 关键部件更换\n");totalCost += 5000;}report.append("预估费用: ¥").append(totalCost).append("\n\n");}@Overridepublic String getReport() {return "总维护费用: ¥" + totalCost + "\n" + report.toString();}
}// 升级访问者
public class UpgradeVisitor implements Visitor {private StringBuilder report = new StringBuilder();@Overridepublic void visitHuman(Human human) {report.append("=== 人类能力提升建议 ===\n");report.append("姓名: ").append(human.getName()).append("\n");if (human.getAge() < 30) {report.append("建议: 专业技能培训\n");report.append("预期效果: 职业技能提升20%\n");} else {report.append("建议: 健康管理课程\n");report.append("预期效果: 寿命延长5年\n");}report.append("\n");}@Overridepublic void visitRobot(Robot robot) {report.append("=== 机器人升级方案 ===\n");report.append("型号: ").append(robot.getModel()).append("\n");if (robot.getOperatingHours() < 5000) {report.append("建议: 软件系统升级\n");report.append("预期效果: 性能提升15%\n");} else {report.append("建议: 硬件模块升级\n");report.append("预期效果: 使用寿命延长3年\n");}report.append("\n");}@Overridepublic String getReport() {return report.toString();}
}
3. 元素接口和具体元素类
// 元素接口
public interface Element {void accept(Visitor visitor);String getName();
}// 人类
public class Human implements Element {private String name;private int age;private double heartRate;private double bodyTemperature;public Human(String name, int age, double heartRate, double bodyTemperature) {this.name = name;this.age = age;this.heartRate = heartRate;this.bodyTemperature = bodyTemperature;}@Overridepublic void accept(Visitor visitor) {visitor.visitHuman(this);}@Overridepublic String getName() {return name;}// Getter方法public int getAge() { return age; }public double getHeartRate() { return heartRate; }public double getBodyTemperature() { return bodyTemperature; }
}// 机器人
public class Robot implements Element {private String model;private String serialNumber;private int batteryLevel;private int operatingHours;public Robot(String model, String serialNumber, int batteryLevel, int operatingHours) {this.model = model;this.serialNumber = serialNumber;this.batteryLevel = batteryLevel;this.operatingHours = operatingHours;}@Overridepublic void accept(Visitor visitor) {visitor.visitRobot(this);}@Overridepublic String getName() {return model + "-" + serialNumber;}// Getter方法public String getModel() { return model; }public String getSerialNumber() { return serialNumber; }public int getBatteryLevel() { return batteryLevel; }public int getOperatingHours() { return operatingHours; }
}
4. 对象结构类
// 对象结构
public class DiagnosticCenter {private List<Element> elements = new ArrayList<>();public void addElement(Element element) {elements.add(element);}public void performDiagnosis(Visitor visitor) {System.out.println("开始诊断检查...\n");for (Element element : elements) {element.accept(visitor);}System.out.println(visitor.getReport());System.out.println("诊断完成\n");}public void showAllElements() {System.out.println("=== 诊断中心成员 ===");for (Element element : elements) {System.out.println("- " + element.getName());}System.out.println();}
}
5. 客户端代码
public class VisitorPatternDemo {public static void main(String[] args) {System.out.println("=== 访问者模式演示 - 人类和机器人诊断系统 ===\n");// 创建诊断中心DiagnosticCenter center = new DiagnosticCenter();// 添加人类和机器人center.addElement(new Human("张三", 35, 85, 36.8));center.addElement(new Human("李四", 45, 95, 37.2));center.addElement(new Robot("T-800", "001", 65, 12000));center.addElement(new Robot("R2-D2", "002", 15, 3000));// 显示所有成员center.showAllElements();// 执行健康检查System.out.println("执行健康检查:");center.performDiagnosis(new HealthCheckVisitor());// 执行维护检查System.out.println("执行维护检查:");center.performDiagnosis(new MaintenanceVisitor());// 执行升级建议System.out.println("执行升级建议:");center.performDiagnosis(new UpgradeVisitor());System.out.println("=== 演示完成 ===");}
}
在Spring的应用
在Spring框架中,访问者模式的应用体现在:
- Bean定义访问:
BeanDefinitionVisitor
用于访问和修改bean定义 - 注解处理:
AnnotationMetadataVisitor
用于访问类的注解信息 - 资源访问:
ResourceVisitor
用于处理资源文件访问逻辑 - 数据访问:JPA和Hibernate中的访问者模式用于实体检查和验证
- 表达式解析:Spring Expression Language (SpEL) 使用访问者模式解析表达式
- AOP处理:切面编程中的访问者模式用于方法拦截和处理
总结
访问者模式通过将操作与对象结构分离,使得可以在不修改现有类的情况下添加新的操作。它特别适用于需要对一个复杂对象结构中的多个元素执行不同操作的场景,如诊断系统、编译器设计、文档处理等。虽然会增加系统复杂性,但提供了很好的扩展性和灵活性。