访问者模式(Visitor Pattern)

访问者模式(Visitor Pattern)

访问者模式是一种行为设计模式,它允许你在不改变对象结构的前提下定义作用于这些对象的新操作。访问者模式通过将操作逻辑从对象结构中分离出来,使得可以在不修改对象类的情况下添加新的操作。

模式结构

  1. 访问者接口(Visitor Interface)
    声明一组访问方法,每个方法对应一个具体元素类。访问者通过这些方法访问元素对象。
  2. 具体访问者类(Concrete Visitor)
    实现访问者接口,定义对各个元素类的具体操作。
  3. 元素接口(Element Interface)
    声明一个 accept 方法,接受访问者对象作为参数。
  4. 具体元素类(Concrete Element)
    实现元素接口,并在 accept 方法中调用访问者的对应方法。
  5. 对象结构(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

应用场景

  1. 复杂对象结构的操作
    当需要对一个复杂对象结构中的所有元素执行某些操作时,可以使用访问者模式。例如,遍历一个包含多种类型对象的集合,并对每个对象执行不同的操作。
  2. 分离辅助行为
    当需要将辅助行为从主要业务逻辑中分离出来时,可以使用访问者模式。访问者模式将非主要行为抽取到一组访问者类中,使得主要类更专注于核心功能。
  3. 特定类的行为
    当某个行为仅在类层次结构中的某些类中有意义时,可以使用访问者模式。访问者模式允许将行为抽取到单独的访问者类中,并只为相关类实现该方法。

在Java中的应用

  1. javax.lang.model.element.ElementVisitor
    Java 的注解处理器 API 中使用了访问者模式。ElementVisitor 接口定义了访问不同类型元素(如类、方法、字段等)的方法。
  2. java.nio.file.FileVisitor
    Java NIO 中的 FileVisitor 接口用于遍历文件系统中的目录和文件,并执行特定操作。

优缺点

优点

  1. 开闭原则
    可以在不修改现有类的情况下引入新的操作,只需添加新的访问者类。
  2. 单一职责原则
    将相关行为集中到一个访问者类中,使得代码更易于维护。
  3. 灵活性
    访问者模式允许在不改变对象结构的情况下添加新的操作。

缺点

  1. 增加新元素类困难
    每次在对象结构中添加新元素类时,都需要修改所有访问者类。
  2. 访问者可能无法访问私有成员
    访问者类可能无法访问元素类的私有成员变量和方法,导致某些操作无法实现。

总结

访问者模式通过将操作逻辑从对象结构中分离出来,使得可以在不修改对象类的情况下添加新的操作。它适用于需要对复杂对象结构执行多种操作的场景,尤其是在需要分离辅助行为或特定类行为时。尽管它可能会增加新元素类的复杂性,但其优点在于提高了代码的灵活性和可维护性。在 Java 中,访问者模式广泛应用于注解处理器和文件系统遍历等场景。