访问者模式(Visitor Pattern)
在我们开发中,有时候会遇到这样一种情况:一个对象结构中有很多不同类型的对象,而我们需要对这些对象执行一些“额外操作”,但又不希望修改这些对象本身的代码。比如:
- 对一组不同类型的文件做压缩、备份等处理
- 对公司组织架构中的员工做统计、加薪等操作
- 编译器中,对语法树节点进行类型检查或代码生成
这时候就可以考虑使用 访问者模式(Visitor Pattern) 来解决。
什么是访问者模式?
访问者模式是一种行为型设计模式,它将数据结构与作用于结构上的操作分离,通过引入访问者对象,将操作从对象内部抽离出去,从而在不修改类的前提下扩展新的行为。
一句话总结就是:让你能在不动原有类结构的基础上,新增对这些类的“外部操作”。
模式结构
访问者模式主要由以下几个角色组成:
- Visitor(访问者):定义对元素的访问操作,每个元素类型一个方法。
- ConcreteVisitor(具体访问者):实现访问操作,定义具体行为。
- Element(元素接口):定义一个
accept(Visitor)方法,用于接收访问者。 - ConcreteElement(具体元素):实现
accept()方法,通常会将自己传回访问者。 - ObjectStructure(对象结构):存储多个元素,提供访问者的入口。
示例:员工访问器
我们以“公司员工”这个例子来讲。公司中有两类员工:程序员和产品经理,我们希望对他们分别进行:
- 打印信息(比如姓名、岗位、薪资)
- 发放年终奖金
我们不希望把打印和奖金发放的逻辑塞进员工类里,否则违反单一职责,也不利于扩展。
员工抽象类
public interface Employee {
void accept(Visitor visitor); // 接收访问者
}
程序员类
public class Engineer implements Employee {
private String name;
private int codeLinesPerDay;
public Engineer(String name, int codeLinesPerDay) {
this.name = name;
this.codeLinesPerDay = codeLinesPerDay;
}
public String getName() {
return name;
}
public int getCodeLinesPerDay() {
return codeLinesPerDay;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 回调访问者
}
}
产品经理类
public class ProductManager implements Employee {
private String name;
private int productCount;
public ProductManager(String name, int productCount) {
this.name = name;
this.productCount = productCount;
}
public String getName() {
return name;
}
public int getProductCount() {
return productCount;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
访问者接口
public interface Visitor {
void visit(Engineer engineer);
void visit(ProductManager pm);
}
具体访问者:打印信息
public class InfoPrinter implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("程序员:" + engineer.getName() + ",每天写 " + engineer.getCodeLinesPerDay() + " 行代码");
}
@Override
public void visit(ProductManager pm) {
System.out.println("产品经理:" + pm.getName() + ",管理 " + pm.getProductCount() + " 个产品");
}
}
具体访问者:发年终奖
public class BonusVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
int bonus = engineer.getCodeLinesPerDay() * 10;
System.out.println("程序员:" + engineer.getName() + " 年终奖:" + bonus);
}
@Override
public void visit(ProductManager pm) {
int bonus = pm.getProductCount() * 1000;
System.out.println("产品经理:" + pm.getName() + " 年终奖:" + bonus);
}
}
员工容器类
import java.util.ArrayList;
import java.util.List;
public class Company {
private List<Employee> employees = new ArrayList<>();
public void add(Employee e) {
employees.add(e);
}
public void show(Visitor visitor) {
for (Employee e : employees) {
e.accept(visitor);
}
}
}
使用示例
public class Main {
public static void main(String[] args) {
Company company = new Company();
company.add(new Engineer("小张", 500));
company.add(new ProductManager("小李", 3));
System.out.println("打印员工信息:");
company.show(new InfoPrinter());
System.out.println("发放年终奖:");
company.show(new BonusVisitor());
}
}
输出结果
打印员工信息:
程序员:小张,每天写 500 行代码
产品经理:小李,管理 3 个产品
发放年终奖:
程序员:小张 年终奖:5000
产品经理:小李 年终奖:3000
小结
访问者模式的核心就是将操作抽离出去,让你在不修改元素类的前提下增加新的行为,非常适合对一组对象结构进行“多种操作”的场景。比如打印、导出、统计、计算、格式转换等都可以作为访问者来扩展。
不过它也有一个缺点,就是每新增一个元素类(比如新增一类员工),你就需要修改所有访问者类,违反了开闭原则中的“对修改关闭”。所以它适用于稳定的数据结构、但多变的行为的场景。
适用场景
- 对对象结构中的元素进行多种操作,如导出、分析、监控
- 编译器中的语法树遍历和处理
- 数据分析系统中的指标收集器
- 元素结构固定,但操作逻辑不断增加的系统