访问者模式 (Visitor Pattern)
什么是访问者模式?
访问者模式是一种行为型设计模式,它允许你在不修改对象结构的前提下定义作用于这些对象的新操作。
简单来说:访问者模式就是"访问者",可以在不修改对象结构的情况下添加新的操作。
生活中的例子
想象一下:
- 博物馆:游客参观不同的展品
- 体检:医生检查不同的身体部位
- 税务:税务局检查不同的收入类型
为什么需要访问者模式?
传统方式的问题
java
// 每次新增操作都需要修改类
class Element {
public void operation1() {}
public void operation2() {}
// 新增操作需要修改类
public void operation3() {}
}
问题:
- 违反开闭原则:修改现有类
- 代码臃肿:类中包含多个操作
- 难以扩展:新增操作需要修改多个类
访问者模式的优势
java
// 使用访问者模式
element.accept(visitor);
优势:
- 符合开闭原则:不修改现有类
- 操作分离:操作与对象结构分离
- 易于扩展:新增操作很容易
访问者模式的结构
┌─────────────────────┐
│ Visitor │ 访问者接口
├─────────────────────┤
│ + visit(ElementA): │
│ void │
│ + visit(ElementB): │
│ void │
└──────────┬──────────┘
│ 实现
│
┌──────────┴──────────┐
│ ConcreteVisitor │ 具体访问者
├─────────────────────┤
│ + visit(ElementA): │
│ void │
│ + visit(ElementB): │
│ void │
└─────────────────────┘
┌─────────────────────┐
│ Element │ 元素接口
├─────────────────────┤
│ + accept(Visitor): │
│ void │
└──────────┬──────────┘
│ 实现
├──┬──────────────────┬──────────────┐
│ │ │
┌──────────┴──────┐ ┌───────────┴───────┐ ┌───┴────────┐
│ ElementA │ │ ElementB │ │ ... │ 具体元素
├─────────────────┤ ├───────────────────┤ ├────────────┤
│ + accept(): void│ │ + accept(): void │ │ │
└─────────────────┘ └───────────────────┘ └────────────┘
代码示例
1. 定义访问者接口
java
/**
* 访问者接口
*/
public interface Visitor {
/**
* 访问图书
*/
void visit(Book book);
/**
* 访问水果
*/
void visit(Fruit fruit);
}
2. 定义元素接口
java
/**
* 元素接口
*/
public interface Element {
/**
* 接受访问者
*/
void accept(Visitor visitor);
}
3. 定义具体元素
java
/**
* 具体元素:图书
*/
public class Book implements Element {
private String name;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 具体元素:水果
*/
public class Fruit implements Element {
private String name;
private double price;
private double weight;
public Fruit(String name, double price, double weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public double getWeight() {
return weight;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
4. 定义具体访问者
java
/**
* 具体访问者:购物车
*/
public class ShoppingCart implements Visitor {
private List<Element> elements = new ArrayList<>();
private double total = 0;
public void addElement(Element element) {
elements.add(element);
}
@Override
public void visit(Book book) {
double price = book.getPrice();
total += price;
System.out.println("图书: " + book.getName() + ",价格: ¥" + price);
}
@Override
public void visit(Fruit fruit) {
double price = fruit.getPrice() * fruit.getWeight();
total += price;
System.out.println("水果: " + fruit.getName() + ",价格: ¥" + price + " (¥" + fruit.getPrice() + "/kg × " + fruit.getWeight() + "kg)");
}
public double getTotal() {
return total;
}
}
5. 使用访问者
java
/**
* 访问者模式测试类
* 演示如何使用访问者模式计算购物车总价
*/
public class VisitorTest {
public static void main(String[] args) {
System.out.println("=== 访问者模式测试 ===\n");
// 创建购物车
ShoppingCart cart = new ShoppingCart();
// 添加商品
Book book1 = new Book("Java编程思想", 89.0);
Book book2 = new Book("设计模式", 69.0);
Fruit apple = new Fruit("苹果", 8.0, 2.5);
Fruit banana = new Fruit("香蕉", 5.0, 3.0);
cart.addElement(book1);
cart.addElement(book2);
cart.addElement(apple);
cart.addElement(banana);
// 计算总价
System.out.println("--- 计算购物车总价 ---");
book1.accept(cart);
book2.accept(cart);
apple.accept(cart);
banana.accept(cart);
System.out.println("\n总价: ¥" + cart.getTotal());
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("1. 编译器:语法树遍历");
System.out.println("2. 文档处理:XML/JSON处理");
System.out.println("3. 代码分析:代码静态分析");
System.out.println("4. 对象结构:复杂对象结构的操作");
System.out.println("\n=== 双分派 ===");
System.out.println("访问者模式使用了双分派:");
System.out.println("- 第一次分派:元素接受访问者");
System.out.println("- 第二次分派:访问者访问元素");
System.out.println("这样可以根据元素类型和访问者类型选择不同的操作");
}
}
访问者模式的优点
- 符合开闭原则:不修改现有类
- 操作分离:操作与对象结构分离
- 易于扩展:新增操作很容易
- 集中管理:相关操作集中管理
访问者模式的缺点
- 增加复杂度:引入了额外的类
- 难以扩展:新增元素需要修改所有访问者
- 依赖具体:访问者依赖具体元素
适用场景
- 对象结构稳定:对象结构相对稳定
- 操作多变:操作经常变化
- 需要分离:需要将操作与对象结构分离
常见应用场景
- 编译器:语法树遍历
- 文档处理:XML/JSON处理
- 代码分析:代码静态分析
使用建议
- 对象结构稳定:使用访问者模式
- 操作多变:使用访问者模式
- 对象结构多变:使用其他模式
注意事项
⚠️ 访问者模式虽然强大,但要注意:
- 对象结构要相对稳定
- 不要让访问者过于复杂
- 考虑使用双分派