访问者模式(Visitor Pattern)
访问者模式(Visitor Pattern)是一种行为设计模式,它允许你在不改变对象结构的前提下,定义作用于这些对象元素的新操作。以下从多个方面详细介绍访问者模式。
模式结构与角色
访问者模式主要包含以下几个角色:
- 抽象访问者(Visitor):定义了一系列访问不同元素的方法,每个方法对应一个具体元素类。
- 具体访问者(Concrete Visitor):实现了抽象访问者接口,提供了对每个元素的具体访问逻辑。
- 抽象元素(Element):定义了一个接受访问者的方法
accept(Visitor visitor)
,用于接收访问者并调用访问者的相应方法。 - 具体元素(Concrete Element):实现了抽象元素接口,在
accept
方法中调用访问者的具体访问方法。 - 对象结构(Object Structure):通常是一个集合,包含多个元素对象,提供了遍历元素的方法。
代码示例
以下是一个简单的访问者模式示例,模拟一个商品结算系统,包含书籍和水果两种商品,不同的访问者可以计算不同的价格:
import java.util.ArrayList;
import java.util.List;
// 抽象访问者
interface Visitor {
void visit(Book book);
void visit(Fruit fruit);
}
// 具体访问者:普通顾客访问者
class NormalCustomerVisitor implements Visitor {
@Override
public void visit(Book book) {
double price = book.getPrice();
System.out.println("普通顾客购买书籍,价格为:" + price + " 元");
}
@Override
public void visit(Fruit fruit) {
double price = fruit.getPricePerKg() * fruit.getWeight();
System.out.println("普通顾客购买水果,价格为:" + price + " 元");
}
}
// 具体访问者:VIP 顾客访问者
class VIPCustomerVisitor implements Visitor {
@Override
public void visit(Book book) {
double price = book.getPrice() * 0.9; // 九折优惠
System.out.println("VIP 顾客购买书籍,价格为:" + price + " 元");
}
@Override
public void visit(Fruit fruit) {
double price = fruit.getPricePerKg() * fruit.getWeight() * 0.8; // 八折优惠
System.out.println("VIP 顾客购买水果,价格为:" + price + " 元");
}
}
// 抽象元素
interface Element {
void accept(Visitor visitor);
}
// 具体元素:书籍
class Book implements Element {
private double price;
public Book(double price) {
this.price = price;
}
public double getPrice() {
return price;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 具体元素:水果
class Fruit implements Element {
private double pricePerKg;
private double weight;
public Fruit(double pricePerKg, double weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
public double getPricePerKg() {
return pricePerKg;
}
public double getWeight() {
return weight;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 对象结构
class ShoppingCart {
private List<Element> items = new ArrayList<>();
public void addItem(Element item) {
items.add(item);
}
public void accept(Visitor visitor) {
for (Element item : items) {
item.accept(visitor);
}
}
}
// 客户端代码
public class VisitorPatternExample {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Book(50));
cart.addItem(new Fruit(10, 2));
Visitor normalCustomer = new NormalCustomerVisitor();
Visitor vipCustomer = new VIPCustomerVisitor();
System.out.println("普通顾客结算:");
cart.accept(normalCustomer);
System.out.println("\nVIP 顾客结算:");
cart.accept(vipCustomer);
}
}
代码解释
- 抽象访问者
Visitor
:定义了visit(Book book)
和visit(Fruit fruit)
两个方法,分别用于访问书籍和水果元素。 - 具体访问者
NormalCustomerVisitor
和VIPCustomerVisitor
:实现了Visitor
接口,提供了普通顾客和 VIP 顾客的不同结算逻辑。 - 抽象元素
Element
:定义了accept(Visitor visitor)
方法,用于接收访问者。 - 具体元素
Book
和Fruit
:实现了Element
接口,在accept
方法中调用访问者的相应visit
方法。 - 对象结构
ShoppingCart
:包含一个元素列表,提供了addItem
方法用于添加元素,以及accept
方法用于遍历元素并调用其accept
方法。 - 客户端代码:创建了购物车,添加了书籍和水果元素,分别使用普通顾客访问者和 VIP 顾客访问者进行结算。
优点
- 可扩展性:可以很容易地添加新的访问者,而不需要修改元素类的代码。例如,如果需要添加一个新的结算规则,只需要创建一个新的具体访问者类即可。
- 分离关注点:将元素的操作逻辑与元素本身分离,使得代码更加清晰,易于维护。
- 集中处理:可以将对不同元素的操作集中在访问者类中,便于管理和维护。
缺点
- 违反开闭原则:如果需要添加新的元素类,需要修改抽象访问者接口及其所有具体访问者类,这违反了开闭原则。
- 增加系统复杂度:访问者模式会增加类的数量,使得系统变得复杂,尤其是在元素类和访问者类较多的情况下。
应用场景
- 数据结构稳定但操作多变的场景:当对象结构比较稳定,但需要经常定义新的操作时,使用访问者模式可以方便地添加新的操作。
- 需要对不同类型的对象进行集中处理的场景:例如,文档处理系统中,需要对不同类型的文档元素(如文本、图片、表格等)进行不同的处理,可以使用访问者模式。
Java设计模式 文章被收录于专栏
设计模式是软件开发中针对反复出现的问题所总结归纳出的通用解决方案,它可以帮助开发者更高效地构建软件系统,提升代码的可维护性、可扩展性和可复用性。