公众号:【CTO成长日记】

Java 设计模式之访问者模式:灵活处理对象集合的操作

在软件开发中,我们经常需要处理由多个不同类型对象组成的集合,并且需要对这些对象执行各种操作。如果将这些操作直接嵌入到对象类中,会导致类的职责过重,且难以扩展新的操作。访问者模式(Visitor Pattern)正是为解决这类问题而生,它能在不修改原有对象结构的前提下,为对象集合添加新的操作。

访问者模式的核心思想

访问者模式属于行为型设计模式,其核心思想是将数据结构与数据操作分离。它定义一个访问者接口来封装对集合中各种元素的操作,使得当需要新增操作时,只需添加新的访问者实现,而无需修改原有元素类。

这种分离带来的好处是:

  • 操作的扩展变得简单,符合开闭原则
  • 将相关操作集中到一个访问者类中,而非分散在各个元素类中
  • 可以在不修改元素类的前提下,增加新的操作

访问者模式的角色组成

访问者模式主要包含以下五个角色:

  1. 抽象访问者(Visitor):声明对所有具体元素类型的访问方法
  2. 具体访问者(ConcreteVisitor):实现抽象访问者声明的方法,定义对具体元素的具体操作
  3. 抽象元素(Element):声明一个接受访问者的方法(accept)
  4. 具体元素(ConcreteElement):实现接受访问者的方法,通常调用访问者的对应访问方法
  5. 对象结构(ObjectStructure):包含元素的集合,提供遍历元素的方法,允许访问者访问所有元素

访问者模式的代码实现

下面我们通过一个公司员工系统的例子来实现访问者模式。假设公司有两种员工:工程师和经理,我们需要对他们执行计算工资、评估绩效等操作。

1. 定义抽象元素和具体元素

// 抽象元素:员工
public abstract class Employee {
    protected String name;  // 姓名
    protected int salary;   // 基本工资
    
    public Employee(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }
    
    // 接受访问者访问
    public abstract void accept(Visitor visitor);
    
    // getter方法
    public String getName() {
        return name;
    }
    
    public int getSalary() {
        return salary;
    }
}

// 具体元素:工程师
public class Engineer extends Employee {
    private int projectBonus;  // 项目奖金
    
    public Engineer(String name, int salary, int projectBonus) {
        super(name, salary);
        this.projectBonus = projectBonus;
    }
    
    public int getProjectBonus() {
        return projectBonus;
    }
    
    @Override
    public void accept(Visitor visitor) {
        // 调用访问者针对工程师的访问方法
        visitor.visit(this);
    }
}

// 具体元素:经理
public class Manager extends Employee {
    private int departmentBonus;  // 部门奖金
    
    public Manager(String name, int salary, int departmentBonus) {
        super(name, salary);
        this.departmentBonus = departmentBonus;
    }
    
    public int getDepartmentBonus() {
        return departmentBonus;
    }
    
    @Override
    public void accept(Visitor visitor) {
        // 调用访问者针对经理的访问方法
        visitor.visit(this);
    }
}

2. 定义抽象访问者和具体访问者

// 抽象访问者
public interface Visitor {
    // 访问工程师
    void visit(Engineer engineer);
    
    // 访问经理
    void visit(Manager manager);
}

// 具体访问者:计算工资
public class SalaryCalculator implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        int totalSalary = engineer.getSalary() + engineer.getProjectBonus();
        System.out.println(engineer.getName() + "(工程师)的总工资:" + totalSalary);
    }
    
    @Override
    public void visit(Manager manager) {
        int totalSalary = manager.getSalary() + manager.getDepartmentBonus();
        System.out.println(manager.getName() + "(经理)的总工资:" + totalSalary);
    }
}

// 具体访问者:评估绩效
public class PerformanceEvaluator implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        // 简单逻辑:项目奖金越高,绩效越好
        String evaluation = engineer.getProjectBonus() > 5000 ? "优秀" : "良好";
        System.out.println(engineer.getName() + "(工程师)的绩效评估:" + evaluation);
    }
    
    @Override
    public void visit(Manager manager) {
        // 简单逻辑:部门奖金越高,绩效越好
        String evaluation = manager.getDepartmentBonus() > 10000 ? "优秀" : "良好";
        System.out.println(manager.getName() + "(经理)的绩效评估:" + evaluation);
    }
}

3. 定义对象结构

// 对象结构:公司
import java.util.ArrayList;
import java.util.List;

public class Company {
    private List<Employee> employees = new ArrayList<>();
    
    // 添加员工
    public void addEmployee(Employee employee) {
        employees.add(employee);
    }
    
    // 移除员工
    public void removeEmployee(Employee employee) {
        employees.remove(employee);
    }
    
    // 接受访问者访问所有员工
    public void accept(Visitor visitor) {
        for (Employee employee : employees) {
            employee.accept(visitor);
        }
    }
}

4. 客户端使用示例

public class Client {
    public static void main(String[] args) {
        // 创建公司
        Company company = new Company();
        
        // 添加员工
        company.addEmployee(new Engineer("张三", 15000, 6000));
        company.addEmployee(new Engineer("李四", 12000, 4000));
        company.addEmployee(new Manager("王五", 25000, 15000));
        company.addEmployee(new Manager("赵六", 20000, 8000));
        
        // 计算工资(使用工资计算访问者)
        System.out.println("===== 工资计算 =====");
        company.accept(new SalaryCalculator());
        
        // 评估绩效(使用绩效评估访问者)
        System.out.println("\n===== 绩效评估 =====");
        company.accept(new PerformanceEvaluator());
    }
}

运行结果:

===== 工资计算 =====
张三(工程师)的总工资:21000
李四(工程师)的总工资:16000
王五(经理)的总工资:40000
赵六(经理)的总工资:28000

===== 绩效评估 =====
张三(工程师)的绩效评估:优秀
李四(工程师)的绩效评估:良好
王五(经理)的绩效评估:优秀
赵六(经理)的绩效评估:良好

Java 中的访问者模式应用

在 Java 标准库中,访问者模式也有不少应用:

  1. 编译器的抽象语法树(AST)处理:编译器在处理代码时会生成 AST,访问者模式常用于遍历 AST 并执行各种操作(如语法检查、代码生成等)
  2. XML 解析:某些 XML 解析器使用访问者模式处理 XML 文档的不同节点
  3. Java 注解处理javax.lang.model.element.ElementVisitor接口是访问者模式的典型应用,用于处理注解相关的元素
  4. 集合框架:虽然不明显,但集合的迭代和操作思想与访问者模式有相似之处

访问者模式的适用场景

  • 当需要对一个由多个不同类型对象组成的集合执行多种不相关的操作时
  • 当这些操作经常需要新增或修改,而对象集合的结构相对稳定时
  • 当需要将相关操作集中管理,而不是分散在各个对象类中时
  • 当需要在不修改对象结构的前提下,为对象集合添加新的操作时

访问者模式的优缺点

优点:

  • 符合单一职责原则,每个访问者专注于一组相关操作
  • 符合开闭原则,新增操作只需添加新的访问者,无需修改元素类
  • 集中管理分散在多个元素类中的操作,便于维护
  • 可以在访问过程中积累状态,实现复杂的操作逻辑

缺点:

  • 增加新的元素类型时,需要修改所有访问者类,违反开闭原则
  • 可能破坏元素类的封装性,因为访问者需要访问元素的内部状态
  • 元素类与访问者类之间存在较强的耦合关系
  • 增加了系统的复杂度,需要定义多个新类

总结

访问者模式通过分离数据结构和数据操作,为处理复杂对象集合提供了灵活的解决方案。它特别适合当对象结构稳定但需要频繁添加新操作的场景,能够有效提高代码的可扩展性和可维护性。

然而,访问者模式也有其局限性,尤其是在需要频繁添加新元素类型的场景中并不适用。在实际开发中,我们需要根据具体情况权衡利弊,合理选择是否使用访问者模式。

理解访问者模式的设计思想,不仅有助于我们在合适的场景中应用它,也能帮助我们更好地理解 Java 标准库中的相关实现,提升代码设计能力。

原文:CTO成长日记

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐