【设计模式】访问者模式
**简介
假设你有一个购物车(对象结构),里面有多种商品(元素),如苹果、牛奶、书籍。每个商品的计价规则不同:
- 水果按重量计价
- 牛奶按数量计价
- 书籍按固定价格计价
现在需要实现两种功能:
- 计算总价
- 打印购物小票
访问者模式的作用:
- 将商品和操作(如计算价格、打印小票)分离。
- 新增功能(如打折计算)时,只需新增一个“访问者”,无需修改商品类。
适用场景:
- 对象结构稳定,但需要频繁新增操作。
- 需要对同一对象结构进行多种独立操作(如计算总价、导出数据)。
优点:
- 符合开闭原则:新增操作无需修改对象结构。
- 操作逻辑集中:每个访问者类负责一个独立功能。
缺点:
- 对象结构变化时,所有访问者类都需要修改。
- 过度使用会导致代码复杂度增加。
代码
// 元素接口:商品
interface ItemElement {
int accept(ShoppingCartVisitor visitor);
}
// 具体元素:书籍
class Book implements ItemElement {
private int price;
private String isbn;
public Book(int price, String isbn) {
this.price = price;
this.isbn = isbn;
}
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
public int getPrice() { return price; }
}
// 具体元素:水果
class Fruit implements ItemElement {
private int pricePerKg;
private int weight;
public Fruit(int pricePerKg, int weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
public int getPricePerKg() { return pricePerKg; }
public int getWeight() { return weight; }
}
// 访问者接口
interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
// 具体访问者:计算总价
class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
public int visit(Book book) {
return book.getPrice();
}
public int visit(Fruit fruit) {
return fruit.getPricePerKg() * fruit.getWeight();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[]{
new Book(100, "ISBN-123"),
new Fruit(10, 2)
};
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int total = 0;
for (ItemElement item : items) {
total += item.accept(visitor);
}
System.out.println("Total cost: " + total); // 输出 120
}
}
类图
@startuml
interface ItemElement {
+ accept(ShoppingCartVisitor visitor): int
}
class Book {
- price: int
- isbn: String
+ accept(ShoppingCartVisitor visitor): int
}
class Fruit {
- pricePerKg: int
- weight: int
+ accept(ShoppingCartVisitor visitor): int
}
interface ShoppingCartVisitor {
+ visit(Book book): int
+ visit(Fruit fruit): int
}
class ShoppingCartVisitorImpl {
+ visit(Book book): int
+ visit(Fruit fruit): int
}
ItemElement <|-- Book
ItemElement <|-- Fruit
ShoppingCartVisitor <|-- ShoppingCartVisitorImpl
Book --> ShoppingCartVisitor
Fruit --> ShoppingCartVisitor
@enduml
4. 实际应用场景案例:编译器语法树分析
场景:编译器需要遍历抽象语法树(AST),进行类型检查、代码生成等操作。
// 表达式元素接口
interface Expr {
<T> T accept(Visitor<T> visitor);
}
// 具体元素:数字表达式
class Number implements Expr {
private int value;
public Number(int value) { this.value = value; }
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public int getValue() { return value; }
}
// 具体元素:加法表达式
class Addition implements Expr {
private Expr left;
private Expr right;
public Addition(Expr left, Expr right) {
this.left = left;
this.right = right;
}
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
public Expr getLeft() { return left; }
public Expr getRight() { return right; }
}
// 访问者接口
interface Visitor<T> {
T visit(Number number);
T visit(Addition addition);
}
// 具体访问者:表达式求值
class EvaluateVisitor implements Visitor<Integer> {
public Integer visit(Number number) {
return number.getValue();
}
public Integer visit(Addition addition) {
return addition.getLeft().accept(this) + addition.getRight().accept(this);
}
}
// 客户端代码
public class CompilerClient {
public static void main(String[] args) {
Expr expr = new Addition(
new Number(10),
new Addition(new Number(20), new Number(30))
);
EvaluateVisitor evaluator = new EvaluateVisitor();
int result = expr.accept(evaluator);
System.out.println("Result: " + result); // 输出 60
}
}