深入理解设计模式之访问者模式(Visitor Pattern)
前言
在面向对象编程中,我们经常遇到这样的场景:需要对一个对象结构中的元素执行各种不同的操作,而这些操作会频繁变化。传统的做法是在每个元素类中添加新的操作方法,但这违反了开闭原则,且会导致类的职责不清。访问者模式(Visitor Pattern)正是为解决这类问题而设计的行为型模式。
本文将深入探讨访问者模式的原理、实现方式,并结合编译器AST遍历、XML/JSON处理等实际生产场景,以及Java编译器、Spring框架等开源项目的应用,帮助读者全面掌握这一重要但相对复杂的设计模式。
一、什么是访问者模式
1.1 定义
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不改变各元素类的前提下,定义作用于这些元素的新操作。访问者模式将数据结构与数据操作分离,使得操作集合可以独立变化。
1.2 核心思想
访问者模式的核心思想是:
- 将操作从元素类中分离出来,封装到独立的访问者类中
- 元素类提供一个接受访问者的接口(accept方法)
- 通过双分派(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 访问者模式的角色
-
Visitor(抽象访问者):为对象结构中每个具体元素类声明一个访问操作接口。
-
ConcreteVisitor(具体访问者):实现抽象访问者声明的接口,定义对每个元素访问时的具体操作。
-
Element(抽象元素):定义一个accept方法,接受一个访问者对象作为参数。
-
ConcreteElement(具体元素):实现accept方法,通常调用访问者的visit方法。
-
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 + "}");
}
// 同样需要修改
}
传统方式的问题:
- 违反开闭原则:每次添加新操作都要修改所有元素类
- 职责不清:元素类既要管理自身数据,又要实现各种操作
- 难以维护:操作逻辑分散在各个元素类中
- 代码重复:相似的操作在不同类中重复实现
2.2 访问者模式的优势
- 扩展性强:添加新操作只需增加新的访问者,无需修改元素类
- 职责分离:元素类专注于数据,访问者类专注于操作
- 操作集中:相关操作集中在一个访问者类中,便于理解和维护
- 符合开闭原则:对扩展开放,对修改关闭
- 复杂对象结构的优势:特别适合对象结构稳定但操作经常变化的场景
三、访问者模式的实现
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 设计原则
- 稳定的元素结构:访问者模式适用于元素类稳定的场景
- 频繁变化的操作:操作经常变化时使用访问者模式最合适
- 单一职责:每个访问者只负责一类操作
- 开闭原则:添加新操作不需要修改元素类
6.2 何时使用访问者模式
适用场景:
- 对象结构稳定,但经常需要定义新的操作
- 需要对对象结构中的元素执行多种不同的操作
- 操作逻辑复杂,放在元素类中会导致类过于臃肿
- 需要对对象结构进行多种不相关的操作
不适用场景:
- 对象结构经常变化(增加新元素类需要修改所有访问者)
- 元素类很少,操作也很少
- 元素类型不固定或使用大量继承
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 常见陷阱
- 违反封装性:访问者需要访问元素的内部数据
java
// 解决方案:提供足够的公共访问方法
class WellEncapsulatedElement implements ComputerComponent {
private String privateData = "secret";
// 提供访问方法而不是暴露字段
public String getDisplayData() {
return "Processed: " + privateData;
}
@Override
public void accept(ComputerVisitor visitor) {
visitor.visit(this);
}
}
- 元素类变化的代价:增加新元素类需要修改所有访问者
java
// 解决方案:提供默认实现或使用适配器
interface FlexibleVisitor {
default void visit(ComputerComponent component) {
// 默认处理
}
void visit(KeyboardComponent keyboard);
// 其他visit方法
}
- 循环依赖:访问者和元素相互依赖
java
// 解决方案:通过接口解耦
interface Visitable {
void accept(Visitor visitor);
}
interface Visitor {
void visit(Visitable element);
}
7.2 性能优化
- 避免重复遍历:缓存访问结果
- 并行访问:对于独立的元素,使用并行流
- 延迟计算:只在需要时才计算结果
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 核心要点
- 双分派机制:通过两次动态分派实现类型安全的操作
- 操作集中:将相关操作集中在访问者类中
- 易于扩展操作:添加新操作只需新增访问者类
- 适合稳定结构:对象结构稳定时效果最好
8.2 优缺点总结
优点:
- 符合单一职责原则,操作与数据分离
- 易于增加新的操作
- 操作逻辑集中,便于维护
- 可以累积状态(访问者可以有实例变量)
- 可以跨越类层次结构
缺点:
- 增加新的元素类困难(需要修改所有访问者)
- 违反了封装性(访问者需要访问元素内部数据)
- 增加了系统复杂度
- 依赖具体类而非抽象
8.3 使用建议
- 评估稳定性:确保对象结构相对稳定
- 操作优先:当操作比元素更容易变化时使用
- 结合其他模式:可与组合模式、迭代器模式结合
- 提供默认实现:减少访问者实现的工作量
- 文档清晰:访问者模式相对复杂,需要良好的文档
8.4 最佳实践
- 小型项目:元素和操作都少时,直接在元素类中实现操作
- 中型项目:对象结构稳定,操作多样时使用访问者模式
- 大型项目:使用ASM、JavaParser等成熟的框架
- 编译器/解释器:访问者模式的最佳应用场景
访问者模式是23种设计模式中较为复杂的一种,但在合适的场景下能发挥巨大作用。特别是在编译器、AST处理、复杂对象结构遍历等领域,访问者模式几乎是标准解决方案。希望本文能帮助你理解并在实际项目中正确应用访问者模式。