访问者模式(Visitor Pattern)核心解析


1. 模式定义

访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。

对于访问者模式,学习的主要难点在代码实现。而代码实现比较复杂的主要原因是,函数重载在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个重载函数,是在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。

正是因为代码实现难理解,所以,在项目中应用这种模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种模式。


2. 核心思想

  • 双重分发(Double Dispatch)​
    通过两次方法调用动态绑定具体操作:

    1. 第一次分发​:对象通过Accept方法将自身类型信息传递给访问者。

    2. 第二次分发​:访问者根据对象类型调用对应的VisitXxx方法。

  • 逆向控制
    对象结构(如商品类)仅需实现Accept方法,而将具体操作逻辑委托给外部访问者。


3. 核心组件

组件

职责

Visitor

声明访问操作的接口(如VisitBookVisitElectronics

ConcreteVisitor

实现具体算法(如折扣计算、数据统计)

Element

定义Accept(Visitor v)方法,作为访问入口

ConcreteElement

实现Accept方法,调用访问者的VisitXxx方法

ObjectStructure

管理元素集合(可选),提供遍历接口


4. 工作流程

sequenceDiagram
    participant Client
    participant ConcreteElementA
    participant ConcreteVisitor
    
    Client->>ConcreteElementA: Accept(Visitor)
    ConcreteElementA->>ConcreteVisitor: VisitConcreteElementA(this)
    ConcreteVisitor-->>ConcreteElementA: 执行操作
    ConcreteVisitor-->>Client: 返回结果

5. 核心优势

  • 扩展性强​:新增操作只需添加访问者类,无需修改元素类。

  • 职责分离​:将易变的操作逻辑从稳定的数据结构中剥离。

  • 集中管理​:相关操作集中在同一访问者中,避免代码分散。


6. 适用场景

一般来说,访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类(Extractor、Compressor)中。

  • 需要对复杂对象结构(如抽象语法树、UI组件树)执行多种独立操作。

  • 对象结构稳定,但需频繁新增或修改操作逻辑。

  • 需要运行时动态切换算法(如不同折扣策略、数据导出格式)。


7. 潜在缺点

  • 破坏封装​:访问者可能需要访问元素内部状态。

  • 增加复杂度​:元素类型变化会导致所有访问者修改。

  • 性能开销​:双重分发机制可能引入额外调用开销。


8. 与其他模式的关系

  • 组合模式​:访问者常用来遍历组合结构。

  • 策略模式​:均封装算法,但访问者处理对象结构,策略处理单一对象。

  • 装饰器模式​:访问者从外部扩展功能,装饰器通过包装对象扩展。


9. 设计哲学

  • 好莱坞原则​:“不要调用我们,我们会调用你”——对象结构通过Accept反向控制操作流程。

  • 开闭原则​:对扩展开放(新增访问者),对修改封闭(不修改元素类)。


10. 经典应用

  • 编译器​:语法树检查、代码生成、优化等不同阶段。

  • 文档处理​:格式转换、字数统计、语法检查等操作。

  • 游戏引擎​:渲染、碰撞检测、AI计算等子系统。

访问者模式通过双重分发机制逆向控制,实现了数据结构与操作逻辑的优雅解耦,是处理复杂对象结构扩展需求的利器。

代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DesignPatternsPractice.Src.Behavioral.VisitorDesignPattern
{
    /// <summary>
    ///  一个商品打折逻辑的案例
    /// </summary>
    public class VisitorDesign2
    {
        static void Main()
        {
            List<IProduct> products = new List<IProduct>()
            {
                new Book{ Price =100},
                new Electronics{ Price = 2000},
                new Food{ Price = 50}
            };
            //节假日 折扣
            var HolidayDiscountVisitor = new HolidayDiscountVisitor();
            foreach (IProduct item in products)
            {
                // 这里是反向调用  把商品类的具体类型传给 折扣逻辑然后折扣逻辑在执行
                item.Accept(HolidayDiscountVisitor);
            }

            // 应用 清仓活动
            var clearanceVisitor = new ClearanceVisitor();
            foreach (var product in products)
            {
                product.Accept(clearanceVisitor);
            }
            
        }
    }

    public interface IProduct
    {
        /// <summary>
        /// 价格
        /// </summary>
        double Price { get; }
        void Accept(IVisitor visitor);
    }
    /// <summary>
    /// 具体商品类 书
    /// </summary>
    public class Book : IProduct
    {
        public double Price { set; get; }


        public void Accept(IVisitor visitor)
        {
            visitor.VisitBook(this);
        }
    }

    public class Electronics : IProduct
    {
        public double Price { set; get; }

        public void Accept(IVisitor visitor)
        {
            visitor.VisitElectronics(this);
        }
    }

    public class Food : IProduct
    {
        public double Price { set; get; }
        public void Accept(IVisitor visitor)
        {
            visitor.VisitFood(this);
        }
    }
    /// <summary>
    /// 访问者接口
    /// </summary>
    public interface IVisitor
    {
        void VisitBook(Book book);
        void VisitElectronics(Electronics electronics);
        void VisitFood(Food food);
    }

    /// <summary>
    /// 具体访问者
    /// 
    /// 节假日折扣访问者
    /// </summary>
    // 
    public class HolidayDiscountVisitor : IVisitor
    {
        public void VisitBook(Book book)
        {
            book.Price *= 0.8; // 图书8折
            Console.WriteLine($"节假日图书折扣价: {book.Price}");
        }

        public void VisitElectronics(Electronics electronics)
        {
            electronics.Price *= 0.9; // 电子产品9折
            Console.WriteLine($"节假日电子产品折扣价: {electronics.Price}");
        }

        public void VisitFood(Food food)
        {
            Console.WriteLine($"食品不参与节假日折扣: {food.Price}");
        }
    }

    // 清仓活动访问者
    public class ClearanceVisitor : IVisitor
    {
        public void VisitBook(Book book)
        {
            book.Price *= 0.5; // 图书5折
            Console.WriteLine($"清仓图书折扣价: {book.Price}");
        }

        public void VisitElectronics(Electronics electronics)
        {
            electronics.Price *= 0.6; // 电子产品6折
            Console.WriteLine($"清仓电子产品折扣价: {electronics.Price}");
        }

        public void VisitFood(Food food)
        {
            food.Price *= 0.8; // 食品8折
            Console.WriteLine($"清仓食品折扣价: {food.Price}");
        }
    }
}