访问者模式(Visitor Pattern)
访问者模式(Visitor Pattern)
访问者模式是一种行为设计模式,它允许你在不改变对象结构的前提下定义作用于这些对象的新操作。访问者模式通过将操作逻辑从对象结构中分离出来,使得可以在不修改对象类的情况下添加新的操作。
模式结构
- 访问者接口(Visitor Interface)
声明一组访问方法,每个方法对应一个具体元素类。访问者通过这些方法访问元素对象。 - 具体访问者类(Concrete Visitor)
实现访问者接口,定义对各个元素类的具体操作。 - 元素接口(Element Interface)
声明一个accept
方法,接受访问者对象作为参数。 - 具体元素类(Concrete Element)
实现元素接口,并在accept
方法中调用访问者的对应方法。 - 对象结构(Object Structure)
包含一组元素对象,通常是一个集合或列表。对象结构可以遍历元素,并调用它们的accept
方法。
代码示例
1. 访问者接口(Visitor Interface)
// 购物车访问者接口
interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
2. 具体访问者类(Concrete Visitor)
// 购物车访问者实现类
class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
@Override
public int visit(Book book) {
int cost;
// 单价超过 50 价格减去 5
if (book.getPrice() > 50) {
cost = book.getPrice() - 5;
} else {
cost = book.getPrice();
}
System.out.println("Book ISBN :: " + book.getIsbnCode() + " cost = " + cost);
return cost;
}
@Override
public int visit(Fruit fruit) {
int cost = fruit.getPricePreKg() * fruit.getWeigh();
System.out.println(fruit.getName() + " cost = " + cost);
return cost;
}
}
3. 元素接口(Element Interface)
// 商品接口
interface ItemElement {
int accept(ShoppingCartVisitor visitor);
}
4. 具体元素类(Concrete Element)
// 图书类
class Book implements ItemElement {
private final int price;
private final String isbnCode;
public Book(int price, String isbnCode) {
this.price = price;
this.isbnCode = isbnCode;
}
public int getPrice() {
return price;
}
public String getIsbnCode() {
return isbnCode;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
// 水果类
class Fruit implements ItemElement {
private final int pricePreKg;
private final int weigh;
private final String name;
public Fruit(int pricePreKg, int weigh, String name) {
this.pricePreKg = pricePreKg;
this.weigh = weigh;
this.name = name;
}
public int getPricePreKg() {
return pricePreKg;
}
public int getWeigh() {
return weigh;
}
public String getName() {
return name;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
5. 调用示例
public class VisitorPattern {
public static void main(String[] args) {
List<ItemElement> items = new ArrayList<>();
items.add(new Book(68, "IK12dd"));
items.add(new Book(32, "GH52oq"));
items.add(new Fruit(15, 3, "Orange"));
items.add(new Fruit(30, 2, "Mango"));
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int total = 0;
for (ItemElement item : items) {
total += item.accept(visitor);
}
System.out.println("Total cost = " + total);
}
}
输出结果
Book ISBN :: IK12dd cost = 63
Book ISBN :: GH52oq cost = 32
Orange cost = 45
Mango cost = 60
Total cost = 200
应用场景
- 复杂对象结构的操作
当需要对一个复杂对象结构中的所有元素执行某些操作时,可以使用访问者模式。例如,遍历一个包含多种类型对象的集合,并对每个对象执行不同的操作。 - 分离辅助行为
当需要将辅助行为从主要业务逻辑中分离出来时,可以使用访问者模式。访问者模式将非主要行为抽取到一组访问者类中,使得主要类更专注于核心功能。 - 特定类的行为
当某个行为仅在类层次结构中的某些类中有意义时,可以使用访问者模式。访问者模式允许将行为抽取到单独的访问者类中,并只为相关类实现该方法。
在Java中的应用
javax.lang.model.element.ElementVisitor
Java 的注解处理器 API 中使用了访问者模式。ElementVisitor
接口定义了访问不同类型元素(如类、方法、字段等)的方法。java.nio.file.FileVisitor
Java NIO 中的FileVisitor
接口用于遍历文件系统中的目录和文件,并执行特定操作。
优缺点
优点
- 开闭原则
可以在不修改现有类的情况下引入新的操作,只需添加新的访问者类。 - 单一职责原则
将相关行为集中到一个访问者类中,使得代码更易于维护。 - 灵活性
访问者模式允许在不改变对象结构的情况下添加新的操作。
缺点
- 增加新元素类困难
每次在对象结构中添加新元素类时,都需要修改所有访问者类。 - 访问者可能无法访问私有成员
访问者类可能无法访问元素类的私有成员变量和方法,导致某些操作无法实现。
总结
访问者模式通过将操作逻辑从对象结构中分离出来,使得可以在不修改对象类的情况下添加新的操作。它适用于需要对复杂对象结构执行多种操作的场景,尤其是在需要分离辅助行为或特定类行为时。尽管它可能会增加新元素类的复杂性,但其优点在于提高了代码的灵活性和可维护性。在 Java 中,访问者模式广泛应用于注解处理器和文件系统遍历等场景。