设计模式之访问者模式详解

深入理解设计模式之访问者模式(Visitor Pattern)

前言

在面向对象编程中,我们经常遇到这样的场景:需要对一个对象结构中的元素执行各种不同的操作,而这些操作会频繁变化。传统的做法是在每个元素类中添加新的操作方法,但这违反了开闭原则,且会导致类的职责不清。访问者模式(Visitor Pattern)正是为解决这类问题而设计的行为型模式。

本文将深入探讨访问者模式的原理、实现方式,并结合编译器AST遍历、XML/JSON处理等实际生产场景,以及Java编译器、Spring框架等开源项目的应用,帮助读者全面掌握这一重要但相对复杂的设计模式。

一、什么是访问者模式

1.1 定义

访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不改变各元素类的前提下,定义作用于这些元素的新操作。访问者模式将数据结构与数据操作分离,使得操作集合可以独立变化。

1.2 核心思想

访问者模式的核心思想是:

  1. 将操作从元素类中分离出来,封装到独立的访问者类中
  2. 元素类提供一个接受访问者的接口(accept方法)
  3. 通过双分派(Double Dispatch)机制实现操作与元素的解耦

1.3 访问者模式的结构

复制代码
                +------------------+
                |     Client       |
                +------------------+
                        |
                        | 使用
            +-----------+-----------+
            |                       |
            ↓                       ↓
    +--------------+        +---------------+
    | ObjectStruct |        |   Visitor     |
    +--------------+        | <<interface>> |
    | + accept()   |        +---------------+
    +--------------+        | + visitA()    |
            |               | + visitB()    |
            | 包含          +---------------+
            ↓                       △
    +--------------+                |
    |   Element    |        +-------+-------+
    | <<interface>>|        |               |
    +--------------+   +---------+    +---------+
    | + accept()   |   |Concrete |    |Concrete |
    +--------------+   |VisitorA |    |VisitorB |
            △          +---------+    +---------+
            |          | + visit()|    | + visit()|
     +------+------+   +---------+    +---------+
     |             |
+----------+ +----------+
|ConcreteA | |ConcreteB |
|Element   | |Element   |
+----------+ +----------+
|+ accept()| |+ accept()|
+----------+ +----------+

1.4 访问者模式的角色

  1. Visitor(抽象访问者):为对象结构中每个具体元素类声明一个访问操作接口。

  2. ConcreteVisitor(具体访问者):实现抽象访问者声明的接口,定义对每个元素访问时的具体操作。

  3. Element(抽象元素):定义一个accept方法,接受一个访问者对象作为参数。

  4. ConcreteElement(具体元素):实现accept方法,通常调用访问者的visit方法。

  5. ObjectStructure(对象结构):能够枚举它的元素,提供一个高层接口允许访问者访问它的元素。

二、为什么需要访问者模式

2.1 传统方式的问题

让我们先看一个不使用访问者模式的例子:

java 复制代码
/**
 * 传统方式:在元素类中直接实现各种操作
 */
abstract class ComputerPart {
    public abstract void displayPrice();
    public abstract void exportToXML();
    public abstract void exportToJSON();
    // 每次新增操作都要修改这个类及其所有子类
}

class Keyboard extends ComputerPart {
    private double price = 50.0;

    @Override
    public void displayPrice() {
        System.out.println("Keyboard price: $" + price);
    }

    @Override
    public void exportToXML() {
        System.out.println("<keyboard price=\"" + price + "\" />");
    }

    @Override
    public void exportToJSON() {
        System.out.println("{\"type\":\"keyboard\", \"price\":" + price + "}");
    }
    // 如果要添加新操作,比如 exportToYAML(),需要修改这个类
}

class Monitor extends ComputerPart {
    private double price = 200.0;

    @Override
    public void displayPrice() {
        System.out.println("Monitor price: $" + price);
    }

    @Override
    public void exportToXML() {
        System.out.println("<monitor price=\"" + price + "\" />");
    }

    @Override
    public void exportToJSON() {
        System.out.println("{\"type\":\"monitor\", \"price\":" + price + "}");
    }
    // 同样需要修改
}

传统方式的问题:

  1. 违反开闭原则:每次添加新操作都要修改所有元素类
  2. 职责不清:元素类既要管理自身数据,又要实现各种操作
  3. 难以维护:操作逻辑分散在各个元素类中
  4. 代码重复:相似的操作在不同类中重复实现

2.2 访问者模式的优势

  1. 扩展性强:添加新操作只需增加新的访问者,无需修改元素类
  2. 职责分离:元素类专注于数据,访问者类专注于操作
  3. 操作集中:相关操作集中在一个访问者类中,便于理解和维护
  4. 符合开闭原则:对扩展开放,对修改关闭
  5. 复杂对象结构的优势:特别适合对象结构稳定但操作经常变化的场景

三、访问者模式的实现

3.1 基础实现示例

让我们使用访问者模式重构电脑配件的例子:

java 复制代码
/**
 * 抽象元素:电脑配件
 */
interface ComputerComponent {
    void accept(ComputerVisitor visitor);
}

/**
 * 具体元素:键盘
 */
class KeyboardComponent implements ComputerComponent {
    private String brand = "罗技";
    private double price = 199.0;

    public String getBrand() {
        return brand;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(ComputerVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 具体元素:鼠标
 */
class MouseComponent implements ComputerComponent {
    private String brand = "雷蛇";
    private double price = 299.0;

    public String getBrand() {
        return brand;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(ComputerVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 具体元素:显示器
 */
class MonitorComponent implements ComputerComponent {
    private String brand = "戴尔";
    private double price = 1999.0;
    private int size = 27;

    public String getBrand() {
        return brand;
    }

    public double getPrice() {
        return price;
    }

    public int getSize() {
        return size;
    }

    @Override
    public void accept(ComputerVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 具体元素:主机
 */
class HostComponent implements ComputerComponent {
    private String cpu = "Intel i7";
    private String gpu = "NVIDIA RTX 3070";
    private double price = 8999.0;

    public String getCpu() {
        return cpu;
    }

    public String getGpu() {
        return gpu;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public void accept(ComputerVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 抽象访问者
 */
interface ComputerVisitor {
    void visit(KeyboardComponent keyboard);
    void visit(MouseComponent mouse);
    void visit(MonitorComponent monitor);
    void visit(HostComponent host);
}

/**
 * 具体访问者:价格显示访问者
 */
class PriceDisplayVisitor implements ComputerVisitor {
    private double totalPrice = 0;

    @Override
    public void visit(KeyboardComponent keyboard) {
        System.out.println("键盘 [" + keyboard.getBrand() + "] 价格: ¥" + keyboard.getPrice());
        totalPrice += keyboard.getPrice();
    }

    @Override
    public void visit(MouseComponent mouse) {
        System.out.println("鼠标 [" + mouse.getBrand() + "] 价格: ¥" + mouse.getPrice());
        totalPrice += mouse.getPrice();
    }

    @Override
    public void visit(MonitorComponent monitor) {
        System.out.println("显示器 [" + monitor.getBrand() + " " + monitor.getSize() +
                         "英寸] 价格: ¥" + monitor.getPrice());
        totalPrice += monitor.getPrice();
    }

    @Override
    public void visit(HostComponent host) {
        System.out.println("主机 [" + host.getCpu() + " + " + host.getGpu() +
                         "] 价格: ¥" + host.getPrice());
        totalPrice += host.getPrice();
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

/**
 * 具体访问者:XML导出访问者
 */
class XMLExportVisitor implements ComputerVisitor {
    private StringBuilder xml = new StringBuilder();

    public XMLExportVisitor() {
        xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        xml.append("<computer>\n");
    }

    @Override
    public void visit(KeyboardComponent keyboard) {
        xml.append("  <keyboard brand=\"").append(keyboard.getBrand())
           .append("\" price=\"").append(keyboard.getPrice()).append("\" />\n");
    }

    @Override
    public void visit(MouseComponent mouse) {
        xml.append("  <mouse brand=\"").append(mouse.getBrand())
           .append("\" price=\"").append(mouse.getPrice()).append("\" />\n");
    }

    @Override
    public void visit(MonitorComponent monitor) {
        xml.append("  <monitor brand=\"").append(monitor.getBrand())
           .append("\" size=\"").append(monitor.getSize())
           .append("\" price=\"").append(monitor.getPrice()).append("\" />\n");
    }

    @Override
    public void visit(HostComponent host) {
        xml.append("  <host cpu=\"").append(host.getCpu())
           .append("\" gpu=\"").append(host.getGpu())
           .append("\" price=\"").append(host.getPrice()).append("\" />\n");
    }

    public String getXML() {
        return xml.toString() + "</computer>";
    }
}

/**
 * 具体访问者:JSON导出访问者
 */
class JSONExportVisitor implements ComputerVisitor {
    private StringBuilder json = new StringBuilder();
    private boolean first = true;

    public JSONExportVisitor() {
        json.append("{\n  \"components\": [\n");
    }

    @Override
    public void visit(KeyboardComponent keyboard) {
        addComma();
        json.append("    {\"type\":\"keyboard\", \"brand\":\"").append(keyboard.getBrand())
           .append("\", \"price\":").append(keyboard.getPrice()).append("}");
    }

    @Override
    public void visit(MouseComponent mouse) {
        addComma();
        json.append("    {\"type\":\"mouse\", \"brand\":\"").append(mouse.getBrand())
           .append("\", \"price\":").append(mouse.getPrice()).append("}");
    }

    @Override
    public void visit(MonitorComponent monitor) {
        addComma();
        json.append("    {\"type\":\"monitor\", \"brand\":\"").append(monitor.getBrand())
           .append("\", \"size\":").append(monitor.getSize())
           .append(", \"price\":").append(monitor.getPrice()).append("}");
    }

    @Override
    public void visit(HostComponent host) {
        addComma();
        json.append("    {\"type\":\"host\", \"cpu\":\"").append(host.getCpu())
           .append("\", \"gpu\":\"").append(host.getGpu())
           .append("\", \"price\":").append(host.getPrice()).append("}");
    }

    private void addComma() {
        if (!first) {
            json.append(",\n");
        }
        first = false;
    }

    public String getJSON() {
        return json.toString() + "\n  ]\n}";
    }
}

/**
 * 对象结构:电脑
 */
class Computer {
    private List<ComputerComponent> components = new ArrayList<>();

    public void addComponent(ComputerComponent component) {
        components.add(component);
    }

    public void accept(ComputerVisitor visitor) {
        for (ComputerComponent component : components) {
            component.accept(visitor);
        }
    }
}

/**
 * 客户端测试
 */
public class VisitorPatternDemo {
    public static void main(String[] args) {
        // 创建电脑及其配件
        Computer computer = new Computer();
        computer.addComponent(new KeyboardComponent());
        computer.addComponent(new MouseComponent());
        computer.addComponent(new MonitorComponent());
        computer.addComponent(new HostComponent());

        System.out.println("========== 价格显示 ==========");
        PriceDisplayVisitor priceVisitor = new PriceDisplayVisitor();
        computer.accept(priceVisitor);
        System.out.println("\n总价: ¥" + priceVisitor.getTotalPrice());

        System.out.println("\n========== XML导出 ==========");
        XMLExportVisitor xmlVisitor = new XMLExportVisitor();
        computer.accept(xmlVisitor);
        System.out.println(xmlVisitor.getXML());

        System.out.println("\n========== JSON导出 ==========");
        JSONExportVisitor jsonVisitor = new JSONExportVisitor();
        computer.accept(jsonVisitor);
        System.out.println(jsonVisitor.getJSON());
    }
}

输出结果:

复制代码
========== 价格显示 ==========
键盘 [罗技] 价格: ¥199.0
鼠标 [雷蛇] 价格: ¥299.0
显示器 [戴尔 27英寸] 价格: ¥1999.0
主机 [Intel i7 + NVIDIA RTX 3070] 价格: ¥8999.0

总价: ¥11496.0

========== XML导出 ==========
<?xml version="1.0" encoding="UTF-8"?>
<computer>
  <keyboard brand="罗技" price="199.0" />
  <mouse brand="雷蛇" price="299.0" />
  <monitor brand="戴尔" size="27" price="1999.0" />
  <host cpu="Intel i7" gpu="NVIDIA RTX 3070" price="8999.0" />
</computer>

========== JSON导出 ==========
{
  "components": [
    {"type":"keyboard", "brand":"罗技", "price":199.0},
    {"type":"mouse", "brand":"雷蛇", "price":299.0},
    {"type":"monitor", "brand":"戴尔", "size":27, "price":1999.0},
    {"type":"host", "cpu":"Intel i7", "gpu":"NVIDIA RTX 3070", "price":8999.0}
  ]
}

3.2 双分派机制

访问者模式的核心是双分派(Double Dispatch)机制:

复制代码
第一次分派(动态):
element.accept(visitor) → 调用具体元素的accept方法

第二次分派(动态):
visitor.visit(this) → 调用具体访问者的visit方法

客户端代码:
ComputerComponent component = new KeyboardComponent();
ComputerVisitor visitor = new PriceDisplayVisitor();
component.accept(visitor);

执行流程:
1. component.accept(visitor)
   → KeyboardComponent.accept(visitor)
2. visitor.visit(this)
   → PriceDisplayVisitor.visit(KeyboardComponent)

四、生产环境中的实际应用

4.1 抽象语法树(AST)遍历

编译器中的AST遍历是访问者模式的经典应用场景:

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 抽象语法树节点接口
 */
interface ASTNode {
    void accept(ASTVisitor visitor);
}

/**
 * 数字字面量节点
 */
class NumberNode implements ASTNode {
    private double value;

    public NumberNode(double value) {
        this.value = value;
    }

    public double getValue() {
        return value;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 变量节点
 */
class VariableNode implements ASTNode {
    private String name;

    public VariableNode(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 二元运算节点
 */
class BinaryOpNode implements ASTNode {
    private ASTNode left;
    private String operator;
    private ASTNode right;

    public BinaryOpNode(ASTNode left, String operator, ASTNode right) {
        this.left = left;
        this.operator = operator;
        this.right = right;
    }

    public ASTNode getLeft() {
        return left;
    }

    public String getOperator() {
        return operator;
    }

    public ASTNode getRight() {
        return right;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 函数调用节点
 */
class FunctionCallNode implements ASTNode {
    private String functionName;
    private List<ASTNode> arguments;

    public FunctionCallNode(String functionName, List<ASTNode> arguments) {
        this.functionName = functionName;
        this.arguments = arguments;
    }

    public String getFunctionName() {
        return functionName;
    }

    public List<ASTNode> getArguments() {
        return arguments;
    }

    @Override
    public void accept(ASTVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * AST访问者接口
 */
interface ASTVisitor {
    void visit(NumberNode node);
    void visit(VariableNode node);
    void visit(BinaryOpNode node);
    void visit(FunctionCallNode node);
}

/**
 * 代码生成访问者
 */
class CodeGeneratorVisitor implements ASTVisitor {
    private StringBuilder code = new StringBuilder();

    @Override
    public void visit(NumberNode node) {
        code.append(node.getValue());
    }

    @Override
    public void visit(VariableNode node) {
        code.append(node.getName());
    }

    @Override
    public void visit(BinaryOpNode node) {
        code.append("(");
        node.getLeft().accept(this);
        code.append(" ").append(node.getOperator()).append(" ");
        node.getRight().accept(this);
        code.append(")");
    }

    @Override
    public void visit(FunctionCallNode node) {
        code.append(node.getFunctionName()).append("(");
        List<ASTNode> args = node.getArguments();
        for (int i = 0; i < args.size(); i++) {
            args.get(i).accept(this);
            if (i < args.size() - 1) {
                code.append(", ");
            }
        }
        code.append(")");
    }

    public String getCode() {
        return code.toString();
    }
}

/**
 * 表达式求值访问者
 */
class EvaluatorVisitor implements ASTVisitor {
    private double result;
    private java.util.Map<String, Double> variables = new java.util.HashMap<>();

    public EvaluatorVisitor() {
        // 初始化一些变量
        variables.put("x", 10.0);
        variables.put("y", 20.0);
    }

    @Override
    public void visit(NumberNode node) {
        result = node.getValue();
    }

    @Override
    public void visit(VariableNode node) {
        result = variables.getOrDefault(node.getName(), 0.0);
    }

    @Override
    public void visit(BinaryOpNode node) {
        node.getLeft().accept(this);
        double leftValue = result;

        node.getRight().accept(this);
        double rightValue = result;

        switch (node.getOperator()) {
            case "+":
                result = leftValue + rightValue;
                break;
            case "-":
                result = leftValue - rightValue;
                break;
            case "*":
                result = leftValue * rightValue;
                break;
            case "/":
                result = leftValue / rightValue;
                break;
        }
    }

    @Override
    public void visit(FunctionCallNode node) {
        // 简化实现,只支持max和min函数
        List<ASTNode> args = node.getArguments();
        if ("max".equals(node.getFunctionName())) {
            double max = Double.MIN_VALUE;
            for (ASTNode arg : args) {
                arg.accept(this);
                max = Math.max(max, result);
            }
            result = max;
        } else if ("min".equals(node.getFunctionName())) {
            double min = Double.MAX_VALUE;
            for (ASTNode arg : args) {
                arg.accept(this);
                min = Math.min(min, result);
            }
            result = min;
        }
    }

    public double getResult() {
        return result;
    }
}

/**
 * 语法树打印访问者
 */
class ASTPrinterVisitor implements ASTVisitor {
    private StringBuilder output = new StringBuilder();
    private int indent = 0;

    @Override
    public void visit(NumberNode node) {
        printIndent();
        output.append("Number: ").append(node.getValue()).append("\n");
    }

    @Override
    public void visit(VariableNode node) {
        printIndent();
        output.append("Variable: ").append(node.getName()).append("\n");
    }

    @Override
    public void visit(BinaryOpNode node) {
        printIndent();
        output.append("BinaryOp: ").append(node.getOperator()).append("\n");
        indent++;
        node.getLeft().accept(this);
        node.getRight().accept(this);
        indent--;
    }

    @Override
    public void visit(FunctionCallNode node) {
        printIndent();
        output.append("FunctionCall: ").append(node.getFunctionName()).append("\n");
        indent++;
        for (ASTNode arg : node.getArguments()) {
            arg.accept(this);
        }
        indent--;
    }

    private void printIndent() {
        for (int i = 0; i < indent; i++) {
            output.append("  ");
        }
    }

    public String getOutput() {
        return output.toString();
    }
}

/**
 * AST测试
 */
public class ASTVisitorDemo {
    public static void main(String[] args) {
        // 构建AST: max(x + 5, y * 2)
        ASTNode ast = new FunctionCallNode("max", List.of(
            new BinaryOpNode(
                new VariableNode("x"),
                "+",
                new NumberNode(5)
            ),
            new BinaryOpNode(
                new VariableNode("y"),
                "*",
                new NumberNode(2)
            )
        ));

        System.out.println("========== AST结构 ==========");
        ASTPrinterVisitor printer = new ASTPrinterVisitor();
        ast.accept(printer);
        System.out.println(printer.getOutput());

        System.out.println("========== 代码生成 ==========");
        CodeGeneratorVisitor codeGen = new CodeGeneratorVisitor();
        ast.accept(codeGen);
        System.out.println("生成的代码: " + codeGen.getCode());

        System.out.println("\n========== 表达式求值 ==========");
        EvaluatorVisitor evaluator = new EvaluatorVisitor();
        ast.accept(evaluator);
        System.out.println("计算结果: " + evaluator.getResult());
        System.out.println("说明: x=10, y=20, 所以 max(10+5, 20*2) = max(15, 40) = 40");
    }
}

4.2 购物车价格计算系统

在电商系统中,不同类型的商品有不同的优惠策略:

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 商品接口
 */
interface Product {
    void accept(ProductVisitor visitor);
    String getName();
    double getBasePrice();
}

/**
 * 图书商品
 */
class Book implements Product {
    private String name;
    private double basePrice;
    private String author;
    private String isbn;

    public Book(String name, double basePrice, String author, String isbn) {
        this.name = name;
        this.basePrice = basePrice;
        this.author = author;
        this.isbn = isbn;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getBasePrice() {
        return basePrice;
    }

    public String getAuthor() {
        return author;
    }

    public String getIsbn() {
        return isbn;
    }

    @Override
    public void accept(ProductVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 电子产品
 */
class Electronic implements Product {
    private String name;
    private double basePrice;
    private String brand;
    private int warrantyMonths;

    public Electronic(String name, double basePrice, String brand, int warrantyMonths) {
        this.name = name;
        this.basePrice = basePrice;
        this.brand = brand;
        this.warrantyMonths = warrantyMonths;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getBasePrice() {
        return basePrice;
    }

    public String getBrand() {
        return brand;
    }

    public int getWarrantyMonths() {
        return warrantyMonths;
    }

    @Override
    public void accept(ProductVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 食品商品
 */
class Food implements Product {
    private String name;
    private double basePrice;
    private String expiryDate;
    private boolean needsRefrigeration;

    public Food(String name, double basePrice, String expiryDate, boolean needsRefrigeration) {
        this.name = name;
        this.basePrice = basePrice;
        this.expiryDate = expiryDate;
        this.needsRefrigeration = needsRefrigeration;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getBasePrice() {
        return basePrice;
    }

    public String getExpiryDate() {
        return expiryDate;
    }

    public boolean needsRefrigeration() {
        return needsRefrigeration;
    }

    @Override
    public void accept(ProductVisitor visitor) {
        visitor.visit(this);
    }
}

/**
 * 商品访问者接口
 */
interface ProductVisitor {
    void visit(Book book);
    void visit(Electronic electronic);
    void visit(Food food);
}

/**
 * 价格计算访问者(普通会员)
 */
class RegularPriceVisitor implements ProductVisitor {
    private double totalPrice = 0;

    @Override
    public void visit(Book book) {
        // 图书9折
        double price = book.getBasePrice() * 0.9;
        System.out.println("《" + book.getName() + "》- 原价: ¥" + book.getBasePrice() +
                         ", 普通会员价(9折): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    @Override
    public void visit(Electronic electronic) {
        // 电子产品95折
        double price = electronic.getBasePrice() * 0.95;
        System.out.println(electronic.getName() + " [" + electronic.getBrand() +
                         "] - 原价: ¥" + electronic.getBasePrice() +
                         ", 普通会员价(95折): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    @Override
    public void visit(Food food) {
        // 食品不打折
        double price = food.getBasePrice();
        System.out.println(food.getName() + " - 原价: ¥" + food.getBasePrice() +
                         ", 普通会员价(无折扣): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

/**
 * 价格计算访问者(VIP会员)
 */
class VIPPriceVisitor implements ProductVisitor {
    private double totalPrice = 0;

    @Override
    public void visit(Book book) {
        // 图书8折
        double price = book.getBasePrice() * 0.8;
        System.out.println("《" + book.getName() + "》- 原价: ¥" + book.getBasePrice() +
                         ", VIP会员价(8折): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    @Override
    public void visit(Electronic electronic) {
        // 电子产品85折
        double price = electronic.getBasePrice() * 0.85;
        System.out.println(electronic.getName() + " [" + electronic.getBrand() +
                         "] - 原价: ¥" + electronic.getBasePrice() +
                         ", VIP会员价(85折): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    @Override
    public void visit(Food food) {
        // 食品95折
        double price = food.getBasePrice() * 0.95;
        System.out.println(food.getName() + " - 原价: ¥" + food.getBasePrice() +
                         ", VIP会员价(95折): ¥" + String.format("%.2f", price));
        totalPrice += price;
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

/**
 * 库存检查访问者
 */
class InventoryCheckVisitor implements ProductVisitor {
    private List<String> warnings = new ArrayList<>();

    @Override
    public void visit(Book book) {
        System.out.println("检查图书库存: " + book.getName() + " [ISBN: " + book.getIsbn() + "]");
    }

    @Override
    public void visit(Electronic electronic) {
        System.out.println("检查电子产品库存: " + electronic.getName() +
                         " [保修: " + electronic.getWarrantyMonths() + "个月]");
    }

    @Override
    public void visit(Food food) {
        System.out.println("检查食品库存: " + food.getName() +
                         " [保质期至: " + food.getExpiryDate() + "]");
        if (food.needsRefrigeration()) {
            warnings.add(food.getName() + " 需要冷藏存储");
        }
    }

    public List<String> getWarnings() {
        return warnings;
    }
}

/**
 * 购物车
 */
class ShoppingCart {
    private List<Product> products = new ArrayList<>();

    public void addProduct(Product product) {
        products.add(product);
    }

    public void accept(ProductVisitor visitor) {
        for (Product product : products) {
            product.accept(visitor);
        }
    }
}

/**
 * 购物车测试
 */
public class ShoppingCartVisitorDemo {
    public static void main(String[] args) {
        // 创建购物车并添加商品
        ShoppingCart cart = new ShoppingCart();
        cart.addProduct(new Book("设计模式", 89.0, "GoF", "978-7-111-21116-6"));
        cart.addProduct(new Book("Java核心技术", 119.0, "Cay S. Horstmann", "978-7-111-44146-3"));
        cart.addProduct(new Electronic("iPhone 15", 5999.0, "Apple", 12));
        cart.addProduct(new Food("有机牛奶", 25.0, "2024-12-31", true));
        cart.addProduct(new Food("巧克力", 18.0, "2025-06-30", false));

        System.out.println("========== 普通会员结算 ==========");
        RegularPriceVisitor regularVisitor = new RegularPriceVisitor();
        cart.accept(regularVisitor);
        System.out.println("\n普通会员总价: ¥" + String.format("%.2f", regularVisitor.getTotalPrice()));

        System.out.println("\n========== VIP会员结算 ==========");
        VIPPriceVisitor vipVisitor = new VIPPriceVisitor();
        cart.accept(vipVisitor);
        System.out.println("\nVIP会员总价: ¥" + String.format("%.2f", vipVisitor.getTotalPrice()));

        System.out.println("\n========== 库存检查 ==========");
        InventoryCheckVisitor inventoryVisitor = new InventoryCheckVisitor();
        cart.accept(inventoryVisitor);
        System.out.println("\n特殊存储要求:");
        for (String warning : inventoryVisitor.getWarnings()) {
            System.out.println("⚠ " + warning);
        }
    }
}

五、开源框架中的访问者模式应用

5.1 Java编译器 - javax.lang.model

Java编译器API中的元素访问者是访问者模式的典型应用:

java 复制代码
/**
 * Java编译器API中的访问者模式示例
 * javax.lang.model.element.ElementVisitor
 */

/*
实际的Java编译器API结构:

public interface ElementVisitor<R, P> {
    R visit(Element e, P p);
    R visitPackage(PackageElement e, P p);
    R visitType(TypeElement e, P p);
    R visitVariable(VariableElement e, P p);
    R visitExecutable(ExecutableElement e, P p);
    R visitTypeParameter(TypeParameterElement e, P p);
}

使用示例:
Element element = ...; // 从编译器获取
element.accept(new SimpleElementVisitor8<Void, Void>() {
    @Override
    public Void visitType(TypeElement e, Void p) {
        System.out.println("访问类型: " + e.getQualifiedName());
        return null;
    }

    @Override
    public Void visitVariable(VariableElement e, Void p) {
        System.out.println("访问变量: " + e.getSimpleName());
        return null;
    }
}, null);
*/

/**
 * 模拟Java编译器元素访问者
 */
public class JavaCompilerVisitorDemo {
    public static void main(String[] args) {
        System.out.println("Java编译器API中的访问者模式:");
        System.out.println("\n接口结构:");
        System.out.println("ElementVisitor<R, P>");
        System.out.println("  ├─ visit(Element e, P p)");
        System.out.println("  ├─ visitPackage(PackageElement e, P p)");
        System.out.println("  ├─ visitType(TypeElement e, P p)");
        System.out.println("  ├─ visitVariable(VariableElement e, P p)");
        System.out.println("  ├─ visitExecutable(ExecutableElement e, P p)");
        System.out.println("  └─ visitTypeParameter(TypeParameterElement e, P p)");

        System.out.println("\n应用场景:");
        System.out.println("1. 注解处理器(Annotation Processor)");
        System.out.println("2. 代码生成工具");
        System.out.println("3. 静态代码分析");
        System.out.println("4. IDE的语法分析");

        System.out.println("\n优势:");
        System.out.println("✓ 无需修改Element类即可添加新的操作");
        System.out.println("✓ 将操作逻辑集中在访问者类中");
        System.out.println("✓ 支持不同类型的返回值和参数");
    }
}

5.2 Spring框架 - BeanDefinitionVisitor

Spring框架使用访问者模式处理Bean定义:

java 复制代码
/**
 * Spring BeanDefinitionVisitor示例
 */
public class SpringVisitorDemo {
    public static void main(String[] args) {
        System.out.println("Spring框架中的访问者模式:");
        System.out.println("\n1. BeanDefinitionVisitor");
        System.out.println("   用途: 遍历并修改BeanDefinition的属性值");
        System.out.println("   场景: 属性占位符解析、属性值转换");

        System.out.println("\n2. 使用场景:");
        System.out.println("   - 解析 ${property} 占位符");
        System.out.println("   - 转换属性值类型");
        System.out.println("   - 验证配置信息");

        System.out.println("\n示例代码结构:");
        System.out.println("BeanDefinitionVisitor visitor = ");
        System.out.println("    new BeanDefinitionVisitor(valueResolver);");
        System.out.println("visitor.visitBeanDefinition(beanDefinition);");

        System.out.println("\n3. 文件访问者模式:");
        System.out.println("   PathMatcher、AntPathMatcher");
        System.out.println("   用于路径匹配和文件遍历");
    }
}

5.3 Apache Commons - FileVisitor

Java NIO.2和Apache Commons都使用了访问者模式处理文件遍历:

java 复制代码
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * 文件访问者示例(基于Java NIO.2)
 */
class FileSearchVisitor extends SimpleFileVisitor<Path> {
    private String targetFileName;
    private int foundCount = 0;

    public FileSearchVisitor(String targetFileName) {
        this.targetFileName = targetFileName;
    }

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        if (file.getFileName().toString().contains(targetFileName)) {
            System.out.println("找到文件: " + file.toAbsolutePath());
            foundCount++;
        }
        return FileVisitResult.CONTINUE;
    }

    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        System.err.println("访问失败: " + file);
        return FileVisitResult.CONTINUE;
    }

    public int getFoundCount() {
        return foundCount;
    }
}

/**
 * 目录大小计算访问者
 */
class DirectorySizeVisitor extends SimpleFileVisitor<Path> {
    private long totalSize = 0;
    private int fileCount = 0;

    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        totalSize += attrs.size();
        fileCount++;
        return FileVisitResult.CONTINUE;
    }

    public long getTotalSize() {
        return totalSize;
    }

    public int getFileCount() {
        return fileCount;
    }
}

/**
 * 文件访问者演示
 */
public class FileVisitorDemo {
    public static void main(String[] args) {
        System.out.println("Java NIO.2 文件访问者模式:\n");
        System.out.println("接口: FileVisitor<Path>");
        System.out.println("  ├─ preVisitDirectory() - 访问目录前");
        System.out.println("  ├─ visitFile() - 访问文件");
        System.out.println("  ├─ visitFileFailed() - 访问失败");
        System.out.println("  └─ postVisitDirectory() - 访问目录后");

        System.out.println("\n实现类: SimpleFileVisitor<Path>");
        System.out.println("提供了默认实现,方便继承");

        System.out.println("\n应用场景:");
        System.out.println("1. 文件搜索");
        System.out.println("2. 目录大小计算");
        System.out.println("3. 文件批量处理");
        System.out.println("4. 目录树打印");

        // 实际使用示例(需要有效的目录路径)
        System.out.println("\n使用示例:");
        System.out.println("Path startPath = Paths.get(\".\");");
        System.out.println("FileSearchVisitor visitor = new FileSearchVisitor(\"*.java\");");
        System.out.println("Files.walkFileTree(startPath, visitor);");
    }
}

六、访问者模式的最佳实践

6.1 设计原则

  1. 稳定的元素结构:访问者模式适用于元素类稳定的场景
  2. 频繁变化的操作:操作经常变化时使用访问者模式最合适
  3. 单一职责:每个访问者只负责一类操作
  4. 开闭原则:添加新操作不需要修改元素类

6.2 何时使用访问者模式

适用场景:

  1. 对象结构稳定,但经常需要定义新的操作
  2. 需要对对象结构中的元素执行多种不同的操作
  3. 操作逻辑复杂,放在元素类中会导致类过于臃肿
  4. 需要对对象结构进行多种不相关的操作

不适用场景:

  1. 对象结构经常变化(增加新元素类需要修改所有访问者)
  2. 元素类很少,操作也很少
  3. 元素类型不固定或使用大量继承

6.3 实现技巧

6.3.1 使用泛型增强访问者
java 复制代码
/**
 * 带返回值的访问者
 */
interface GenericVisitor<R> {
    R visit(KeyboardComponent keyboard);
    R visit(MouseComponent mouse);
    R visit(MonitorComponent monitor);
}

/**
 * 统计访问者
 */
class StatisticsVisitor implements GenericVisitor<Integer> {
    private int count = 0;

    @Override
    public Integer visit(KeyboardComponent keyboard) {
        count++;
        return count;
    }

    @Override
    public Integer visit(MouseComponent mouse) {
        count++;
        return count;
    }

    @Override
    public Integer visit(MonitorComponent monitor) {
        count++;
        return count;
    }

    public int getTotalCount() {
        return count;
    }
}
6.3.2 使用访问者链
java 复制代码
/**
 * 访问者链模式
 */
abstract class ChainableVisitor implements ComputerVisitor {
    protected ComputerVisitor nextVisitor;

    public void setNext(ComputerVisitor visitor) {
        this.nextVisitor = visitor;
    }

    protected void invokeNext(ComputerComponent component) {
        if (nextVisitor != null) {
            component.accept(nextVisitor);
        }
    }
}

/**
 * 验证访问者
 */
class ValidationVisitor extends ChainableVisitor {
    @Override
    public void visit(KeyboardComponent keyboard) {
        System.out.println("验证键盘: " + keyboard.getBrand());
        invokeNext(keyboard);
    }

    @Override
    public void visit(MouseComponent mouse) {
        System.out.println("验证鼠标: " + mouse.getBrand());
        invokeNext(mouse);
    }

    @Override
    public void visit(MonitorComponent monitor) {
        System.out.println("验证显示器: " + monitor.getBrand());
        invokeNext(monitor);
    }

    @Override
    public void visit(HostComponent host) {
        System.out.println("验证主机: " + host.getCpu());
        invokeNext(host);
    }
}
6.3.3 反射方式实现访问者
java 复制代码
/**
 * 基于反射的访问者(简化版)
 * 避免为每个元素类型写一个visit方法
 */
abstract class ReflectiveVisitor {
    public void visit(Object element) {
        try {
            // 通过反射查找对应的visit方法
            java.lang.reflect.Method method = getClass().getMethod(
                "visit" + element.getClass().getSimpleName(),
                element.getClass()
            );
            method.invoke(this, element);
        } catch (Exception e) {
            // 如果没有对应的方法,调用默认方法
            visitDefault(element);
        }
    }

    protected void visitDefault(Object element) {
        System.out.println("访问元素: " + element.getClass().getSimpleName());
    }
}

七、访问者模式的注意事项

7.1 常见陷阱

  1. 违反封装性:访问者需要访问元素的内部数据
java 复制代码
// 解决方案:提供足够的公共访问方法
class WellEncapsulatedElement implements ComputerComponent {
    private String privateData = "secret";

    // 提供访问方法而不是暴露字段
    public String getDisplayData() {
        return "Processed: " + privateData;
    }

    @Override
    public void accept(ComputerVisitor visitor) {
        visitor.visit(this);
    }
}
  1. 元素类变化的代价:增加新元素类需要修改所有访问者
java 复制代码
// 解决方案:提供默认实现或使用适配器
interface FlexibleVisitor {
    default void visit(ComputerComponent component) {
        // 默认处理
    }

    void visit(KeyboardComponent keyboard);
    // 其他visit方法
}
  1. 循环依赖:访问者和元素相互依赖
java 复制代码
// 解决方案:通过接口解耦
interface Visitable {
    void accept(Visitor visitor);
}

interface Visitor {
    void visit(Visitable element);
}

7.2 性能优化

  1. 避免重复遍历:缓存访问结果
  2. 并行访问:对于独立的元素,使用并行流
  3. 延迟计算:只在需要时才计算结果
java 复制代码
/**
 * 缓存结果的访问者
 */
class CachedVisitor implements ComputerVisitor {
    private Map<ComputerComponent, Double> cache = new HashMap<>();

    @Override
    public void visit(KeyboardComponent keyboard) {
        cache.computeIfAbsent(keyboard, k -> computePrice(keyboard));
    }

    private double computePrice(KeyboardComponent keyboard) {
        // 复杂的计算逻辑
        return keyboard.getPrice();
    }

    // 其他visit方法...
}

八、总结

访问者模式是一种强大但相对复杂的设计模式,它通过将操作从元素类中分离出来,实现了操作与数据结构的解耦,使得添加新操作变得容易。

8.1 核心要点

  1. 双分派机制:通过两次动态分派实现类型安全的操作
  2. 操作集中:将相关操作集中在访问者类中
  3. 易于扩展操作:添加新操作只需新增访问者类
  4. 适合稳定结构:对象结构稳定时效果最好

8.2 优缺点总结

优点:

  • 符合单一职责原则,操作与数据分离
  • 易于增加新的操作
  • 操作逻辑集中,便于维护
  • 可以累积状态(访问者可以有实例变量)
  • 可以跨越类层次结构

缺点:

  • 增加新的元素类困难(需要修改所有访问者)
  • 违反了封装性(访问者需要访问元素内部数据)
  • 增加了系统复杂度
  • 依赖具体类而非抽象

8.3 使用建议

  1. 评估稳定性:确保对象结构相对稳定
  2. 操作优先:当操作比元素更容易变化时使用
  3. 结合其他模式:可与组合模式、迭代器模式结合
  4. 提供默认实现:减少访问者实现的工作量
  5. 文档清晰:访问者模式相对复杂,需要良好的文档

8.4 最佳实践

  • 小型项目:元素和操作都少时,直接在元素类中实现操作
  • 中型项目:对象结构稳定,操作多样时使用访问者模式
  • 大型项目:使用ASM、JavaParser等成熟的框架
  • 编译器/解释器:访问者模式的最佳应用场景

访问者模式是23种设计模式中较为复杂的一种,但在合适的场景下能发挥巨大作用。特别是在编译器、AST处理、复杂对象结构遍历等领域,访问者模式几乎是标准解决方案。希望本文能帮助你理解并在实际项目中正确应用访问者模式。


相关推荐
Jomurphys1 小时前
设计模式 - 享元模式 Flyweight Pattern
android·设计模式·享元模式
Jomurphys1 小时前
设计模式 - 组合模式 Composite Pattern
android·设计模式·组合模式
小杨快跑~15 小时前
从装饰者到桥接再到工厂:模式组合的艺术
java·开发语言·设计模式
海中有金15 小时前
设计模式[2]——抽象工厂模式一分钟说清
设计模式·抽象工厂模式
海中有金17 小时前
设计模式[1]——分类&口诀
设计模式·原型模式
雨中飘荡的记忆21 小时前
设计模式之代理模式详解
设计模式·代理模式
繁华似锦respect1 天前
C++ 设计模式之观察者模式详细介绍
linux·开发语言·c++·windows·观察者模式·设计模式·visual studio
雨中飘荡的记忆1 天前
设计模式之适配器模式详解
java·设计模式·适配器模式
phdsky1 天前
【设计模式】工厂方法模式
c++·设计模式·工厂方法模式