访问者模式:把数据结构与数据操作分离,在不修改元素类的前提下,给元素增加新操作。核心角色:
Element(元素接口):接收访问者
accept(Visitor)ConcreteElement:具体元素,实现 accept,调用 visitor.visit (this)
Visitor(访问者接口):为每一种具体元素声明 visit 方法
ConcreteVisitor:具体访问者,实现对每个元素的业务逻辑
ObjectStructure:对象结构,持有元素集合,提供遍历
import java.util.ArrayList;
import java.util.List;
// ---------------------- 1. 访问者接口 Visitor ----------------------
interface Visitor {
void visit(Engineer engineer);
void visit(Manager manager);
}
// ---------------------- 2. 元素接口 Element ----------------------
interface Employee {
// 接收访问者
void accept(Visitor visitor);
}
// ---------------------- 3. 具体元素:工程师 Engineer ----------------------
class Engineer implements Employee {
private String name;
private int workHours;
public Engineer(String name, int workHours) {
this.name = name;
this.workHours = workHours;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return name;
}
public int getWorkHours() {
return workHours;
}
}
// ---------------------- 4. 具体元素:经理 Manager ----------------------
class Manager implements Employee {
private String name;
private int productCount;
public Manager(String name, int productCount) {
this.name = name;
this.productCount = productCount;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return name;
}
public int getProductCount() {
return productCount;
}
}
// ---------------------- 5. 具体访问者1:薪资计算访问者 ----------------------
class SalaryVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
// 工程师:工时 * 150
double salary = engineer.getWorkHours() * 150;
System.out.printf("工程师【%s】薪资:%.2f 元%n", engineer.getName(), salary);
}
@Override
public void visit(Manager manager) {
// 经理:项目数 * 5000
double salary = manager.getProductCount() * 5000;
System.out.printf("经理【%s】薪资:%.2f 元%n", manager.getName(), salary);
}
}
// ---------------------- 6. 具体访问者2:KPI考核访问者(新增操作,不用改Employee) ----------------------
class KpiVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
String kpi = engineer.getWorkHours() > 160 ? "优秀" : "合格";
System.out.printf("工程师【%s】KPI评价:%s%n", engineer.getName(), kpi);
}
@Override
public void visit(Manager manager) {
String kpi = manager.getProductCount() >= 3 ? "优秀" : "合格";
System.out.printf("经理【%s】KPI评价:%s%n", manager.getName(), kpi);
}
}
// ---------------------- 7. 对象结构:员工集合,提供遍历 ----------------------
class EmployeeStructure {
private final List<Employee> employeeList = new ArrayList<>();
public void add(Employee employee) {
employeeList.add(employee);
}
// 让所有元素接受访问者
public void accept(Visitor visitor) {
for (Employee emp : employeeList) {
emp.accept(visitor);
}
}
}
// ---------------------- 测试客户端 ----------------------
public class VisitorDemo {
public static void main(String[] args) {
EmployeeStructure structure = new EmployeeStructure();
structure.add(new Engineer("张三", 180));
structure.add(new Engineer("李四", 140));
structure.add(new Manager("王总", 4));
structure.add(new Manager("赵总", 2));
System.out.println("===== 计算薪资 =====");
structure.accept(new SalaryVisitor());
System.out.println("\n===== KPI考核 =====");
structure.accept(new KpiVisitor());
}
}
输出结果
===== 计算薪资 =====
工程师【张三】薪资:27000.00 元
工程师【李四】薪资:21000.00 元
经理【王总】薪资:20000.00 元
经理【赵总】薪资:10000.00 元
===== KPI考核 =====
工程师【张三】KPI评价:优秀
工程师【李四】KPI评价:合格
经理【王总】KPI评价:优秀
经理【赵总】KPI评价:合格
模式要点
✅ 优点
新增操作只新增 Visitor 实现,不需要修改元素类,符合开闭原则
相关业务逻辑收拢在 Visitor 类中
❌ 缺点
如果要新增一个
Employee子类(比如HR),Visitor 接口和所有访问者实现都要增加visit(HR),改动很大
适用场景:元素类固定很少变化,但操作经常扩展;比如 AST 语法树、文件目录遍历、报表统计。
调用流程
ObjectStructure 遍历集合调用
element.accept(visitor)ConcreteElement.accept 内部执行
visitor.visit(this),触发访问者对应重载方法Visitor 拿到具体元素对象,执行业务逻辑
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问