访问者模式(Visitor Pattern)

访问者模式是 23 种设计模式中的一种行为型模式,它的核心思想是将数据结构与数据操作分离,使得可以在不修改原有数据结构的前提下,为数据结构中的元素添加新的操作。这种模式通过一个 “访问者” 对象封装对元素的操作,从而实现操作的灵活扩展。

核心原理

抽象访问者(Visitor)
声明对每一种具体元素的访问操作(visit(ConcreteElementA)、visit(ConcreteElementB)等)
每个方法对应一种元素类型的操作
具体访问者(ConcreteVisitor)
实现抽象访问者声明的所有方法
定义对具体元素的具体操作逻辑
抽象元素(Element)
声明一个接受访问者的方法(accept(Visitor visitor))
该方法通常会调用访问者的对应方法(visitor.visit(this))
具体元素(ConcreteElement)
实现抽象元素的accept()方法
表示数据结构中的具体元素
对象结构(ObjectStructure)
存储元素集合,提供遍历元素的方法
可以是集合、列表、树等数据结构

访问者模式的核心是 **“双分派” 机制 **:元素的accept()方法接收访问者,然后调用访问者的visit()方法并传入自身,从而确定具体的操作逻辑。

Java 实践示例

下面以 “商品购物车” 为例实现访问者模式:
购物车中有不同类型的商品(书籍、电子产品)
需要对商品执行不同操作(计算价格、生成订单信息)

package com.example.demo;

import java.util.ArrayList;
import java.util.List;

public class VisitorPattern {
    public static void main(String[] args) {
        // 创建商品
        Element book = new Book("设计模式", "Gamma等", 89.0);
        Element phone = new Electronic("智能手机", 4999.0, 2);
        Element laptop = new Electronic("笔记本电脑", 7999.0, 3);

        // 创建购物车并添加商品
        ShoppingCart cart = new ShoppingCart();
        cart.addProduct(book);
        cart.addProduct(phone);
        cart.addProduct(laptop);

        // 使用价格计算器访问购物车
        PriceCalculator priceVisitor = new PriceCalculator();
        cart.accept(priceVisitor);
        System.out.println("总价:" + priceVisitor.getTotalPrice());

        System.out.println("\n--- 订单信息 ---");
        // 使用订单生成器访问购物车
        OrderGenerator orderVisitor = new OrderGenerator();
        cart.accept(orderVisitor);
        System.out.println(orderVisitor.getOrderInfo());
    }

    // 抽象访问者接口
    public interface Visitor {
        // 访问书籍商品
        void visit(Book book);

        // 访问电子产品
        void visit(Electronic electronic);
    }

    // 抽象元素接口
    public interface Element {
        // 接受访问者访问
        void accept(Visitor visitor);
    }

    // 具体元素:电子产品
    public static class Electronic implements Element {
        private String name;
        private double price;
        private int warrantyPeriod; // 保修年限

        public Electronic(String name, double price, int warrantyPeriod) {
            this.name = name;
            this.price = price;
            this.warrantyPeriod = warrantyPeriod;
        }

        // getter方法
        public String getName() { return name; }
        public double getPrice() { return price; }
        public int getWarrantyPeriod() { return warrantyPeriod; }

        @Override
        public void accept(Visitor visitor) {
            // 调用访问者的对应方法
            visitor.visit(this);
        }
    }

    // 具体元素:书籍
    public static class Book implements Element {
        private String name;
        private String author;
        private double price;

        public Book(String name, String author, double price) {
            this.name = name;
            this.author = author;
            this.price = price;
        }

        // getter方法
        public String getName() { return name; }
        public String getAuthor() { return author; }
        public double getPrice() { return price; }

        @Override
        public void accept(Visitor visitor) {
            // 调用访问者的对应方法
            visitor.visit(this);
        }
    }

    //对象结构
    public static class ShoppingCart {
        private List<Element> products = new ArrayList<>();

        // 添加商品
        public void addProduct(Element product) {
            products.add(product);
        }

        // 移除商品
        public void removeProduct(Element product) {
            products.remove(product);
        }

        // 让访问者访问所有商品
        public void accept(Visitor visitor) {
            for (Element product : products) {
                product.accept(visitor);
            }
        }
    }

    // 具体访问者:计算价格
    public static class PriceCalculator implements Visitor {
        private double totalPrice;

        @Override
        public void visit(Book book) {
            // 书籍有折扣
            double price = book.getPrice() * 0.9;
            totalPrice += price;
            System.out.println("书籍《" + book.getName() + "》折后价:" + price);
        }

        @Override
        public void visit(Electronic electronic) {
            // 电子产品有税费
            double price = electronic.getPrice() * 1.1;
            totalPrice += price;
            System.out.println("电子产品" + electronic.getName() + "含税价:" + price);
        }

        public double getTotalPrice() {
            return totalPrice;
        }
    }

    // 具体访问者:生成订单信息
    public static class OrderGenerator implements Visitor {
        private StringBuilder orderInfo = new StringBuilder();

        @Override
        public void visit(Book book) {
            orderInfo.append("书籍:").append(book.getName())
                    .append(",作者:").append(book.getAuthor()).append("\n");
        }

        @Override
        public void visit(Electronic electronic) {
            orderInfo.append("电子产品:").append(electronic.getName())
                    .append(",保修:").append(electronic.getWarrantyPeriod()).append("年\n");
        }

        public String getOrderInfo() {
            return orderInfo.toString();
        }
    }




}

代码解析

访问者相关
Visitor接口定义了对两种商品的访问方法
PriceCalculator实现了计算价格的逻辑(书籍打折、电子产品含税)
OrderGenerator实现了生成订单信息的逻辑
元素相关
Element接口声明了accept()方法
Book和Electronic实现了accept(),并调用访问者对应的visit()方法
对象结构
ShoppingCart作为对象结构,存储商品集合并提供遍历接口

运行结果:

书籍《设计模式》折后价:80.1
电子产品智能手机含税价:5498.9
电子产品笔记本电脑含税价:8798.9
总价:14377.9

--- 订单信息 ---
书籍:设计模式,作者:Gamma等
电子产品:智能手机,保修:2年
电子产品:笔记本电脑,保修:3年

访问者模式的应用场景

访问者模式适用于数据结构稳定,但需要频繁添加新操作的场景,典型应用包括:
数据结构与操作分离
当需要对集合中的不同元素执行多种不相关操作(如统计、过滤、转换)时
例:对文档中的文本、图片、表格等元素执行 “导出 PDF”、“字数统计”、“格式检查” 等操作
复杂对象结构的操作
当对象结构包含多种类型元素,且需要统一处理这些元素时
例:AST(抽象语法树)的遍历,对不同语法节点(表达式、语句、变量)执行分析、优化、生成代码等操作
报表生成系统
对相同的数据源生成不同类型报表(表格、图表、统计数据)
例:销售数据可被 “月度报表访问者”、“区域对比访问者”、“利润分析访问者” 处理
编译器设计
编译器的语义分析、中间代码生成、优化等阶段,可通过访问者模式处理语法树节点
维护遗留系统
当数据结构难以修改(如第三方库、历史代码),但需要添加新功能时,访问者模式可避免修改原有代码

访问者模式的优缺点

优点:
符合开闭原则,新增操作只需添加新的访问者,无需修改元素类
集中管理多种操作,避免操作逻辑分散在各个元素类中
便于实现对不同元素的差异化处理
缺点:
增加新元素类型时,需修改所有访问者接口和实现类,违反开闭原则
元素类需要暴露内部状态给访问者,可能破坏封装性
依赖关系复杂,理解和维护成本较高
访问者模式适合在元素类型稳定,但操作多变的场景中使用,是 “单一职责原则” 和 “开闭原则” 在特定场景下的平衡选择。

更多推荐