Java访问者模式
外观
访问者模式(Visitor Pattern)是设计模式中一种行为型模式,它允许你将算法与对象结构分离,从而在不修改现有对象结构的情况下定义新的操作。该模式通过将操作逻辑移动到独立的访问者类中,实现了对象结构与操作的解耦。
概述[编辑 | 编辑源代码]
访问者模式的核心思想是:在对象结构中定义一个接受访问者的接口,让访问者能够处理结构中的各个元素。这种模式特别适用于以下场景:
- 对象结构包含许多不同类型的对象
- 需要对对象结构中的元素执行多种不相关的操作
- 不希望频繁修改对象结构的类定义
模式结构[编辑 | 编辑源代码]
访问者模式包含以下主要角色:
- Visitor(访问者接口) - 声明访问操作
- ConcreteVisitor(具体访问者) - 实现访问操作
- Element(元素接口) - 定义接受访问者的方法
- ConcreteElement(具体元素) - 实现接受访问者的方法
- ObjectStructure(对象结构) - 维护元素集合,提供遍历接口
代码示例[编辑 | 编辑源代码]
以下是一个简单的访问者模式实现示例,展示如何计算购物车中不同类型商品的总价:
// 元素接口
interface ItemElement {
int accept(ShoppingCartVisitor visitor);
}
// 具体元素类
class Book implements ItemElement {
private int price;
private String isbn;
public Book(int cost, String isbn) {
this.price = cost;
this.isbn = isbn;
}
public int getPrice() {
return price;
}
public String getIsbn() {
return isbn;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
class Fruit implements ItemElement {
private int pricePerKg;
private int weight;
private String name;
public Fruit(int priceKg, int wt, String nm) {
this.pricePerKg = priceKg;
this.weight = wt;
this.name = nm;
}
public int getPricePerKg() {
return pricePerKg;
}
public int getWeight() {
return weight;
}
public String getName() {
return name;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
// 访问者接口
interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
// 具体访问者
class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
@Override
public int visit(Book book) {
int cost = book.getPrice();
// 书籍价格超过50元有5元折扣
if(cost > 50) {
cost -= 5;
}
System.out.println("Book ISBN::" + book.getIsbn() + " cost = " + cost);
return cost;
}
@Override
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg() * fruit.getWeight();
System.out.println(fruit.getName() + " cost = " + cost);
return cost;
}
}
// 对象结构
class ShoppingCart {
private List<ItemElement> items = new ArrayList<>();
public void addItem(ItemElement item) {
items.add(item);
}
public int calculateTotal(ShoppingCartVisitor visitor) {
int sum = 0;
for(ItemElement item : items) {
sum += item.accept(visitor);
}
return sum;
}
}
// 客户端代码
public class VisitorPatternDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Book(100, "1234"));
cart.addItem(new Book(40, "5678"));
cart.addItem(new Fruit(10, 2, "Apple"));
cart.addItem(new Fruit(5, 3, "Banana"));
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int total = cart.calculateTotal(visitor);
System.out.println("Total Cost = " + total);
}
}
输出结果:
Book ISBN::1234 cost = 95 Book ISBN::5678 cost = 40 Apple cost = 20 Banana cost = 15 Total Cost = 170
实际应用场景[编辑 | 编辑源代码]
访问者模式在以下场景中特别有用:
1. 编译器设计:在抽象语法树(AST)上执行各种操作(类型检查、代码优化、代码生成等) 2. 文档处理:对文档结构(段落、表格、图片等)执行多种操作(拼写检查、字数统计、格式转换) 3. UI组件处理:对UI组件树执行多种操作(渲染、布局计算、事件处理) 4. 财务系统:对不同类型的财务记录执行多种计算(税收计算、利润分析、审计)
编译器案例[编辑 | 编辑源代码]
在编译器实现中,访问者模式可以优雅地处理抽象语法树上的各种操作:
// 表达式节点接口
interface ExprNode {
void accept(ExprVisitor visitor);
}
// 具体表达式节点
class NumberNode implements ExprNode {
private int value;
public NumberNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public void accept(ExprVisitor visitor) {
visitor.visit(this);
}
}
class AddNode implements ExprNode {
private ExprNode left, right;
public AddNode(ExprNode l, ExprNode r) {
left = l;
right = r;
}
public ExprNode getLeft() {
return left;
}
public ExprNode getRight() {
return right;
}
@Override
public void accept(ExprVisitor visitor) {
visitor.visit(this);
}
}
// 访问者接口
interface ExprVisitor {
void visit(NumberNode node);
void visit(AddNode node);
}
// 打印访问者
class PrintVisitor implements ExprVisitor {
@Override
public void visit(NumberNode node) {
System.out.print(node.getValue());
}
@Override
public void visit(AddNode node) {
node.getLeft().accept(this);
System.out.print(" + ");
node.getRight().accept(this);
}
}
// 计算访问者
class CalculateVisitor implements ExprVisitor {
private int result;
public int getResult() {
return result;
}
@Override
public void visit(NumberNode node) {
result = node.getValue();
}
@Override
public void visit(AddNode node) {
node.getLeft().accept(this);
int left = result;
node.getRight().accept(this);
result = left + result;
}
}
// 使用示例
public class CompilerExample {
public static void main(String[] args) {
// 构建表达式树: 1 + (2 + 3)
ExprNode expr = new AddNode(
new NumberNode(1),
new AddNode(new NumberNode(2), new NumberNode(3))
);
// 打印表达式
PrintVisitor printer = new PrintVisitor();
expr.accept(printer); // 输出: 1 + 2 + 3
// 计算表达式
CalculateVisitor calculator = new CalculateVisitor();
expr.accept(calculator);
System.out.println(" = " + calculator.getResult()); // 输出: = 6
}
}
优缺点[编辑 | 编辑源代码]
优点[编辑 | 编辑源代码]
- 开闭原则:可以引入新的访问者而无需修改现有元素类
- 单一职责原则:将相关行为集中在一个访问者类中
- 灵活性:可以在运行时选择不同的访问者来执行不同的操作
- 可扩展性:容易添加新的操作
缺点[编辑 | 编辑源代码]
- 破坏封装:访问者通常需要访问元素的内部状态
- 元素接口变更困难:添加新元素类型需要修改所有访问者
- 可能违反依赖倒置原则:具体元素类可能依赖于具体访问者
数学表示[编辑 | 编辑源代码]
访问者模式可以形式化表示为:
与其他模式的关系[编辑 | 编辑源代码]
- 访问者模式可以看作命令模式的扩展版本,其对象可以操作多个不同类型的接收者
- 可以结合组合模式使用,对复杂对象结构进行操作
- 访问者模式经常与迭代器模式一起使用,遍历对象结构并对其元素执行操作
最佳实践[编辑 | 编辑源代码]
1. 当对象结构稳定但需要频繁添加新操作时,优先考虑访问者模式 2. 避免在元素接口中暴露过多内部状态,保持良好封装 3. 考虑使用访问者模式来实现"双重分发"机制 4. 对于简单的对象结构或很少变化的操作集,访问者模式可能过度设计
总结[编辑 | 编辑源代码]
访问者模式提供了一种优雅的方式来分离对象结构和操作逻辑,特别适合处理复杂对象结构上的多种操作。虽然它有一定的复杂性,但在适当的场景下能显著提高代码的可维护性和扩展性。理解并合理应用访问者模式,可以帮助开发者构建更加灵活和可扩展的软件系统。