# C\+\+ 访问者模式(Visitor Pattern)详解 访问者模式是经典**行为型设计模式**,也是 23 种设计模式中最复杂、理解门槛最高的模式之一,核心思想是**将「数据结构」与「作用于数据上的操作」彻底解耦**,在不修改元素类层次结构的前提下,为一组不同类型的元素动态新增操作逻辑。 它的核心适用前提非常明确:**对象结构(元素类型)稳定很少变化,但需要频繁新增操作**。典型场景如编译器 AST 节点的语义分析 / 优化 / 代码生成、文件系统节点的统计 / 搜索 / 导出、图像处理图层的渲染 / 导出 / 校验等。 --- ## 一、解决的核心痛点:操作膨胀与类污染 假设你有一套稳定的图层类层次(栅格图层、矢量图层、文字图层),业务初期只有「渲染」一种操作;随着迭代,陆续要新增「内存统计」「格式导出」「参数校验」「分辨率调整」等十几种操作。 如果把所有操作都写进图层类里,会产生三个严重问题: 1. **类职责爆炸**:每个图层类塞满了各种操作代码,从数据类变成了 “万能类”,可读性和可维护性极差 2. **违反开闭原则**:每新增一种操作,都要修改所有图层类,极易引入 bug 3. **操作无法复用**:操作逻辑和图层强绑定,其他模块无法复用相同的操作逻辑 访问者模式的解法: 把所有操作逻辑从元素类中抽离,每个操作做成一个独立的「访问者」类;元素类只保留一个统一的 `accept` 接口,接收访问者并把自身传递给访问者。新增操作时只需新增访问者类,元素类永远不需要修改。 --- ## 二、核心原理:双分派机制 访问者模式的灵魂是**双分派(Double Dispatch)**,这也是它最难理解的地方。 ### 1\. 什么是单分派 C\+\+ 普通虚函数是**单分派**:最终调用哪个函数,只由「调用者的实际类型」决定。比如 `layer->render()`,只根据 `layer` 指向的实际类型(栅格 / 矢量)调用对应版本,只做一次动态绑定。 ### 2\. 什么是双分派 最终调用的函数,由**两个类型共同决定**:元素的实际类型 \+ 访问者的实际类型。C\+\+ 本身不直接支持双分派,访问者模式通过「两次虚函数调用」模拟实现: 1. **第一次分派**:调用元素的 `accept(visitor)`,根据元素实际类型,进入对应具体元素的 `accept` 实现 2. **第二次分派**:在 `accept` 内部调用 `visitor.visit(*this)`,根据访问者实际类型,进入对应具体访问者的 `visit` 重载 两次动态绑定后,就精准匹配到了「某类元素 \+ 某类操作」的具体逻辑,这就是双分派的本质。 --- ## 三、核心角色 标准访问者模式包含 5 个核心角色: |角色|定位|核心职责| |---|---|---| |**抽象元素(Element)**|元素族的统一接口|声明 `accept` 方法,接收访问者对象| |**具体元素(Concrete Element)**|具体数据类|实现 `accept` 方法,内部调用访问者的 `visit` 并把自身传入| |**抽象访问者(Visitor)**|操作族的统一接口|为每一种具体元素声明一个 `visit` 重载函数| |**具体访问者(Concrete Visitor)**|具体操作实现|实现所有 `visit` 重载,封装一种完整的操作逻辑,可内部累积状态| |**对象结构(Object Structure)**|元素的容器 / 集合|管理元素集合,提供遍历入口,通常是组合模式的容器、列表、树结构| --- ## 四、经典 C\+\+ 实现(继承多态版) 结合你熟悉的**图像处理图层场景**实现标准访问者模式:元素是三类图层,访问者是「渲染」「内存统计」「导出」三种操作。 ### 完整可运行代码 ```cpp #include #include #include #include // ============================================== // 前置声明:元素和访问者互相依赖,需要先声明 // ============================================== class RasterLayer; class VectorLayer; class TextLayer; // ============================================== // 抽象访问者:为每个具体元素声明 visit 重载 // ============================================== class LayerVisitor { public: virtual ~LayerVisitor() = default; virtual void visit(RasterLayer& layer) = 0; virtual void visit(VectorLayer& layer) = 0; virtual void visit(TextLayer& layer) = 0; }; // ============================================== // 抽象元素:统一的图层接口,声明 accept // ============================================== class ImageLayer { public: virtual ~ImageLayer() = default; virtual void accept(LayerVisitor& visitor) = 0; // 接收访问者 virtual std::string getName() const = 0; }; // ============================================== // 具体元素:三类图层 // ============================================== // 具体元素A:栅格图层 class RasterLayer : public ImageLayer { public: explicit RasterLayer(std::string name, int pixelCount) : name_(std::move(name)), pixel_count_(pixelCount) {} // 核心:双分派的第一次跳转,把自身传给访问者 void accept(LayerVisitor& visitor) override { visitor.visit(*this); // 调用对应 visit 重载,第二次分派 } std::string getName() const override { return name_; } int getPixelCount() const { return pixel_count_; } int getBytesPerPixel() const { return 2; } // 16位灰度图 private: std::string name_; int pixel_count_; // 像素总数 }; // 具体元素B:矢量图层 class VectorLayer : public ImageLayer { public: explicit VectorLayer(std::string name, int shapeCount) : name_(std::move(name)), shape_count_(shapeCount) {} void accept(LayerVisitor& visitor) override { visitor.visit(*this); } std::string getName() const override { return name_; } int getShapeCount() const { return shape_count_; } private: std::string name_; int shape_count_; // 矢量图形数量 }; // 具体元素C:文字图层 class TextLayer : public ImageLayer { public: explicit TextLayer(std::string name, int charCount) : name_(std::move(name)), char_count_(charCount) {} void accept(LayerVisitor& visitor) override { visitor.visit(*this); } std::string getName() const override { return name_; } int getCharCount() const { return char_count_; } private: std::string name_; int char_count_; // 字符数量 }; // ============================================== // 具体访问者:三种不同操作 // ============================================== // 具体访问者1:渲染操作 class RenderVisitor : public LayerVisitor { public: void visit(RasterLayer& layer) override { std::cout << "[渲染] 栅格图层:" << layer.getName() << ",执行像素光栅化绘制" << std::endl; } void visit(VectorLayer& layer) override { std::cout << "[渲染] 矢量图层:" << layer.getName() << ",执行路径描边填充" << std::endl; } void visit(TextLayer& layer) override { std::cout << "[渲染] 文字图层:" << layer.getName() << ",执行字体光栅化绘制" << std::endl; } }; // 具体访问者2:内存统计操作(带状态累积) class MemoryStatVisitor : public LayerVisitor { public: void visit(RasterLayer& layer) override { total_bytes_ += layer.getPixelCount() * layer.getBytesPerPixel(); std::cout << "[统计] 栅格图层 " << layer.getName() << " 占用:" << layer.getPixelCount() * layer.getBytesPerPixel() << " 字节" << std::endl; } void visit(VectorLayer& layer) override { total_bytes_ += layer.getShapeCount() * 128; // 每个图形预估128字节 std::cout << "[统计] 矢量图层 " << layer.getName() << " 占用:" << layer.getShapeCount() * 128 << " 字节" << std::endl; } void visit(TextLayer& layer) override { total_bytes_ += layer.getCharCount() * 4; // 每个字符4字节 std::cout << "[统计] 文字图层 " << layer.getName() << " 占用:" << layer.getCharCount() * 4 << " 字节" << std::endl; } size_t getTotalBytes() const { return total_bytes_; } private: size_t total_bytes_ = 0; // 访问者内部累积状态 }; // 具体访问者3:导出操作 class ExportVisitor : public LayerVisitor { public: void visit(RasterLayer& layer) override { std::cout << "[导出] 栅格图层 " << layer.getName() << " → PNG格式" << std::endl; } void visit(VectorLayer& layer) override { std::cout << "[导出] 矢量图层 " << layer.getName() << " → SVG格式" << std::endl; } void visit(TextLayer& layer) override { std::cout << "[导出] 文字图层 " << layer.getName() << " → 文本轮廓" << std::endl; } }; // ============================================== // 对象结构:图层组(组合模式容器,负责遍历元素) // ============================================== class LayerGroup { public: void addLayer(std::shared_ptr layer) { layers_.push_back(std::move(layer)); } // 统一接收访问者,遍历所有子图层并传递访问者 void accept(LayerVisitor& visitor) { for (auto& layer : layers_) { layer->accept(visitor); } } private: std::vector> layers_; }; // ============================================== // 客户端使用示例 // ============================================== int main() { // 1. 构建对象结构 LayerGroup group; group.addLayer(std::make_shared("背景层", 1920 * 1080)); group.addLayer(std::make_shared("图形层", 256)); group.addLayer(std::make_shared("标题层", 128)); std::cout << "===== 1. 执行渲染操作 =====" << std::endl; RenderVisitor render_visitor; group.accept(render_visitor); std::cout << "\n===== 2. 执行内存统计 =====" << std::endl; MemoryStatVisitor stat_visitor; group.accept(stat_visitor); std::cout << "总内存占用:" << stat_visitor.getTotalBytes() << " 字节" << std::endl; std::cout << "\n===== 3. 执行导出操作 =====" << std::endl; ExportVisitor export_visitor; group.accept(export_visitor); return 0; } ``` ### 执行流程说明 当调用 `group.accept(render_visitor)` 时: 1. 图层组遍历每个子图层,调用 `layer->accept(render_visitor)` 2. 以栅格图层为例,进入 `RasterLayer::accept`,调用 `render_visitor.visit(*this)` 3. 编译器根据 `*this` 的类型(`RasterLayer&`)匹配到 `RenderVisitor::visit(RasterLayer&)` 重载 4. 最终执行栅格图层的渲染逻辑 新增操作时,只需新增一个继承 `LayerVisitor` 的类,实现三个 `visit` 方法即可,完全不用修改任何图层类。 --- ## 五、现代 C\+\+ 实现:std::variant 轻量版 C\+\+17 引入的 `std::variant` \+ `std::visit` 是**语言级原生支持的访问者模式**,不需要继承、不需要虚函数、没有运行时开销,代码更简洁,是现代 C\+\+ 开发的首选轻量方案。 ### 适用场景 - 元素类型固定、编译期可确定,不需要运行时动态扩展元素 - 追求零虚函数开销、更轻量的实现 - 不想引入复杂的类继承体系 ### 完整示例代码 ```cpp #include #include #include #include // ============================================== // 元素类型:普通结构体,无需继承、无需虚函数 // ============================================== struct RasterLayer { std::string name; int pixel_count; }; struct VectorLayer { std::string name; int shape_count; }; struct TextLayer { std::string name; int char_count; }; // 元素类型集合:variant 定义所有可能的元素类型 using Layer = std::variant; // ============================================== // 访问者1:渲染操作(重载 lambda 写法) // ============================================== struct RenderVisitor { void operator()(const RasterLayer& layer) const { std::cout << "[渲染] 栅格图层:" << layer.name << std::endl; } void operator()(const VectorLayer& layer) const { std::cout << "[渲染] 矢量图层:" << layer.name << std::endl; } void operator()(const TextLayer& layer) const { std::cout << "[渲染] 文字图层:" << layer.name << std::endl; } }; // ============================================== // 客户端使用 // ============================================== int main() { std::vector layers = { RasterLayer{"背景层", 1920 * 1080}, VectorLayer{"图形层", 256}, TextLayer{"标题层", 128} }; std::cout << "===== variant 版渲染 =====" << std::endl; RenderVisitor render; for (auto& layer : layers) { std::visit(render, layer); // 原生访问者语法 } // 更灵活的写法:直接用重载 lambda 就地定义访问者 std::cout << "\n===== 就地lambda访问者:统计 =====" << std::endl; size_t total = 0; auto stat_visitor = [&](auto&& layer) { using T = std::decay_t; if constexpr (std::is_same_v) { total += layer.pixel_count * 2; } else if constexpr (std::is_same_v) { total += layer.shape_count * 128; } else if constexpr (std::is_same_v) { total += layer.char_count * 4; } }; for (auto& layer : layers) { std::visit(stat_visitor, layer); } std::cout << "总内存:" << total << " 字节" << std::endl; return 0; } ``` ### 两种实现选型对比 |维度|经典继承多态版|std::variant 现代版| |---|---|---| |元素扩展|运行期可动态新增元素类(但需修改所有访问者)|编译期固定元素类型,不可运行期新增| |性能|两次虚函数调用,轻微开销|编译期派发,零运行时开销| |代码量|类层次多,代码量较大|无需继承,代码精简| |封装性|元素可控制暴露的接口|元素是普通结构体,访问者可直接访问所有成员| |适用场景|大型框架、元素需要多态、运行期动态创建|中小型项目、元素类型固定、追求性能| --- ## 六、关键设计注意事项 ### 1\. 元素结构必须稳定是大前提 访问者模式最大的局限性:**新增元素类型非常痛苦**。每加一个元素,所有访问者都要新增对应的 `visit` 重载,违反开闭原则。 - ✅ 元素类型基本不变,操作频繁增加 → 非常适合用访问者 - ❌ 元素类型经常变,操作很少增加 → 绝对不要用访问者 ### 2\. 访问者可以累积状态 访问者可以持有内部成员变量,遍历过程中累积结果(比如统计总内存、校验错误总数),非常适合做聚合、统计、校验类操作。 ### 3\. 封装性的权衡 访问者需要访问元素的内部数据才能完成操作,这就要求元素暴露足够多的 public 接口,或者把访问者设为友元,一定程度上破坏了元素的封装性。设计时要平衡操作灵活性和数据封装性。 ### 4\. 不要在访问中修改对象结构 遍历对象结构的过程中,不要执行删除、新增元素的操作,否则可能导致迭代器失效、重复访问或漏访问。 ### 5\. 常与组合模式搭配 访问者模式 \+ 组合模式是非常经典的组合:组合模式负责构建树形层级结构,访问者负责为树中所有节点提供可扩展的操作,比如文件系统、UI 组件树、图层系统。 --- ## 七、优缺点分析 ### 优点 1. **开闭原则优秀**:新增操作只需新增访问者类,完全不用修改元素和对象结构 2. **职责单一**:操作逻辑从数据类中抽离,元素类只负责数据,访问者只负责操作 3. **支持状态累积**:天然适合统计、校验、汇总类操作 4. **一次遍历执行多操作**:同一次遍历可以传入多个访问者,执行多种操作,效率高 ### 缺点 1. **新增元素成本极高**:每加一个元素,所有访问者都要同步修改 2. **一定程度破坏封装**:访问者需要访问元素内部数据,削弱了元素的封装性 3. **学习门槛高**:双分派逻辑相对抽象,新手理解成本高 4. 经典实现有轻微的虚函数调用开销 --- ## 八、适用场景 1. 对象结构(元素类型)稳定,但需要频繁增加新的操作 2. 需要对一组不同类型的对象执行多种不同的操作,且不希望操作污染数据类 3. 需要跨对象累积状态、做聚合统计、全局校验 4. 结构复杂的树形 / 组合结构,需要支持可扩展的遍历操作 典型工业场景: - 编译器 / 解释器:AST 节点的语义分析、优化、代码生成 - UI 框架:组件的主题渲染、布局计算、事件分发 - 文件系统:目录 / 文件的统计、搜索、格式转换 - 图像处理:图层的渲染、导出、校验、批量参数调整 --- ## 九、易混淆模式对比 ### 访问者模式 vs 策略模式 |维度|访问者模式|策略模式| |---|---|---| |作用对象|一组不同类型的元素|同一个上下文对象| |核心目标|为元素族新增可扩展的操作|为单个对象替换算法实现| |分派机制|双分派,依赖元素 \+ 访问者两个类型|单分派,仅依赖策略类型| |状态累积|支持访问者内部累积状态|通常无状态,单次算法执行| ### 访问者模式 vs 迭代器模式 - 迭代器模式:解决「如何遍历集合」的问题,不关心执行什么操作 - 访问者模式:解决「如何扩展操作」的问题,不关心遍历方式 - 二者经常配合使用:迭代器负责遍历,访问者负责对每个元素执行操作 ----- C++ 访问者模式详解 访问者模式(Visitor Pattern)是**行为型设计模式**之一,核心思想是**将数据结构与作用于数据结构上的操作分离**,在不修改元素类的前提下,为元素体系动态新增操作。它是设计模式中最复杂的模式之一,本质是通过**双分派(Double Dispatch)** 实现 “元素类型 + 操作类型” 的二维多态。 ## 一、核心思想与双分派机制 ### 1. 解决的问题 当一个类层次结构(多种元素类型)需要支持多种业务操作时,如果把所有操作都写在元素类里,会导致: - 元素类职责臃肿,耦合大量无关业务逻辑; - 新增操作必须修改所有元素类,违反开闭原则。 访问者模式把操作抽离为独立的 “访问者” 类,元素只提供 “接受访问” 的入口,操作逻辑全部由访问者实现。 ### 2. 双分派原理 普通多态是**单分派**:调用哪个方法只由**调用者的实际类型**决定(虚函数机制)。 双分派需要两次动态绑定,最终执行的逻辑由**两个对象的实际类型**共同决定: 1. **第一次分派**:调用 `element->accept(visitor)`,根据元素的实际类型,匹配到对应具体元素的 `accept` 实现; 2. **第二次分派**:在 `accept` 内部调用 `visitor->visit(this)`,根据访问者的实际类型,匹配到对应具体访问者的 `visit` 实现。 最终效果:同一种元素遇到不同访问者会执行不同逻辑,同一个访问者访问不同元素也会执行不同逻辑。 ## 二、模式角色与结构 访问者模式包含 5 个核心角色: | 角色 | 作用 | | --------------------------------- | ------------------------------------------------------- | | **Element(抽象元素)** | 声明 `accept(Visitor*)` 接口,是所有被访问元素的基类 | | **ConcreteElement(具体元素)** | 实现 `accept` 方法,内部调用访问者的 `visit` 并传入自身 | | **Visitor(抽象访问者)** | 为每一种具体元素声明一个 `visit` 重载方法 | | **ConcreteVisitor(具体访问者)** | 实现所有 `visit` 方法,封装针对每种元素的业务逻辑 | | **ObjectStructure(对象结构)** | 管理元素集合,提供遍历入口,让访问者批量访问所有元素 | ## 三、传统 C++ 实现(继承 + 虚函数版) 这是经典的 GoF 访问者模式实现,依赖继承和虚函数完成双分派。 ### 示例场景 有圆形(Circle)、矩形(Rectangle)两种图形元素,需要支持 “计算面积”“计算周长” 两种操作,且未来可能新增更多操作。 ```cpp #include #include #include // 前向声明:抽象访问者 class Visitor; // 1. 抽象元素 class Shape { public: virtual ~Shape() = default; // 接受访问者的入口 virtual void accept(Visitor* visitor) = 0; }; // 2. 抽象访问者:为每个具体元素声明visit方法 class Visitor { public: virtual ~Visitor() = default; virtual void visit(class Circle* circle) = 0; virtual void visit(class Rectangle* rectangle) = 0; }; // 3. 具体元素:圆形 class Circle : public Shape { public: double radius; Circle(double r) : radius(r) {} void accept(Visitor* visitor) override { // 第二次分派:调用访问者的visit,传入this(确定元素类型) visitor->visit(this); } }; // 具体元素:矩形 class Rectangle : public Shape { public: double width, height; Rectangle(double w, double h) : width(w), height(h) {} void accept(Visitor* visitor) override { visitor->visit(this); } }; // 4. 具体访问者:计算面积 class AreaVisitor : public Visitor { public: void visit(Circle* circle) override { double area = 3.14159 * circle->radius * circle->radius; std::cout << "圆形面积: " << area << std::endl; } void visit(Rectangle* rectangle) override { double area = rectangle->width * rectangle->height; std::cout << "矩形面积: " << area << std::endl; } }; // 具体访问者:计算周长 class PerimeterVisitor : public Visitor { public: void visit(Circle* circle) override { double perimeter = 2 * 3.14159 * circle->radius; std::cout << "圆形周长: " << perimeter << std::endl; } void visit(Rectangle* rectangle) override { double perimeter = 2 * (rectangle->width + rectangle->height); std::cout << "矩形周长: " << perimeter << std::endl; } }; // 5. 对象结构:管理元素集合 class ShapeCollection { private: std::vector> shapes; public: void addShape(std::unique_ptr shape) { shapes.push_back(std::move(shape)); } // 让访问者遍历所有元素 void acceptAll(Visitor* visitor) { for (auto& shape : shapes) { shape->accept(visitor); } } }; // 客户端使用 int main() { ShapeCollection collection; collection.addShape(std::make_unique(5.0)); collection.addShape(std::make_unique(4.0, 6.0)); AreaVisitor areaCalc; PerimeterVisitor perimeterCalc; std::cout << "=== 计算面积 ===" << std::endl; collection.acceptAll(&areaCalc); std::cout << "\n=== 计算周长 ===" << std::endl; collection.acceptAll(&perimeterCalc); return 0; } ``` ### 输出结果 ```Plain === 计算面积 === 圆形面积: 78.5397 矩形面积: 24 === 计算周长 === 圆形周长: 31.4159 矩形周长: 20 ``` **关键特性**:新增操作时,只需要新增一个 `Visitor` 子类,完全不需要修改 `Shape` 体系的任何代码,符合开闭原则。 ## 四、现代 C++ 实现(std::variant + std::visit) C++17 引入了 `std::variant`(类型安全的联合体)和 `std::visit`,可以**无需继承、无需虚函数**实现访问者模式,是现代 C++ 更推荐的轻量方案。 ### 核心原理 - 用 `std::variant` 替代继承体系的抽象元素,它可以存放其中任意一种类型; - 用**重载了** **`operator()`** **的结构体 / 泛型 lambda** 替代访问者类; - `std::visit` 会在编译期根据 variant 持有的实际类型,自动匹配对应的 `operator()` 重载,实现静态多态。 ### 代码实现 ```cpp #include #include #include // 具体元素:无需继承基类 struct Circle { double radius; Circle(double r) : radius(r) {} }; struct Rectangle { double width, height; Rectangle(double w, double h) : width(w), height(h) {} }; // 元素类型:variant 替代继承体系 using Shape = std::variant; // 具体访问者:重载 operator() struct AreaVisitor { double operator()(const Circle& c) const { return 3.14159 * c.radius * c.radius; } double operator()(const Rectangle& r) const { return r.width * r.height; } }; struct PerimeterVisitor { double operator()(const Circle& c) const { return 2 * 3.14159 * c.radius; } double operator()(const Rectangle& r) const { return 2 * (r.width + r.height); } }; int main() { std::vector shapes = { Circle(5.0), Rectangle(4.0, 6.0) }; AreaVisitor areaCalc; PerimeterVisitor perimeterCalc; std::cout << "=== 计算面积 ===" << std::endl; for (const auto& s : shapes) { double res = std::visit(areaCalc, s); std::cout << "面积: " << res << std::endl; } std::cout << "\n=== 计算周长 ===" << std::endl; for (const auto& s : shapes) { double res = std::visit(perimeterCalc, s); std::cout << "周长: " << res << std::endl; } return 0; } ``` ### 更简洁的写法:泛型 lambda + 重载 对于简单逻辑,可以直接用 lambda 作为访问者,配合 C++20 的 `std::overload`(或自行实现): ```cpp // C++20 可直接用 std::overload template struct overload : Ts... { using Ts::operator()...; }; template overload(Ts...) -> overload; int main() { Shape s = Circle(3.0); // 直接传入多个 lambda 作为访问者 double area = std::visit(overload{ [](const Circle& c) { return 3.14159 * c.radius * c.radius; }, [](const Rectangle& r) { return r.width * r.height; } }, s); return 0; } ``` ## 五、适用场景 1. **数据结构稳定,操作频繁变化**:元素类型基本固定,但需要不断新增业务操作(如编译器 AST 节点,需要支持语法检查、代码生成、优化、格式化等多种操作)。 2. **操作与数据结构解耦**:不希望业务逻辑污染数据类,保持数据类的单一职责。 3. **需要跨类累积状态**:访问者可以在遍历多个元素的过程中维护内部状态(如统计报表、汇总计算)。 4. **对象结构包含多种类型元素**:需要对不同类型元素执行差异化处理,且类型数量相对固定。 ## 六、优缺点分析 ### 优点 1. **开闭原则(对操作友好)**:新增操作只需新增访问者,无需修改元素类。 2. **职责单一**:数据结构只负责数据存储,业务逻辑集中在访问者中。 3. **双分派能力**:支持 “元素类型 × 操作类型” 的二维多态,灵活度高。 4. **状态累积**:访问者对象可在遍历过程中保存中间状态,适合批量计算。 ### 缺点 1. **违反开闭原则(对元素不友好)**:新增一种元素类型时,**所有访问者都必须新增对应的** **`visit`** **方法**,改动成本极高。 2. **破坏封装性**:访问者需要获取元素的内部数据才能完成操作,元素往往需要暴露成员变量或提供 getter,削弱了封装性。 3. **传统实现复杂度高**:继承体系 + 双分派逻辑增加了代码理解成本,新手门槛较高。 4. **传统实现有性能开销**:两次虚函数调用,相比普通函数有额外开销。 ## 七、两种实现方式对比 | 维度 | 传统继承版(GoF) | 现代 variant 版 | | ---------- | ------------------------------ | ----------------------------------------- | | 依赖 | 继承 + 虚函数 | 标准库类型,无继承 | | 多态类型 | 运行时动态多态 | 编译期静态多态 | | 性能 | 两次虚函数调用,有开销 | 无虚函数,编译期派发,性能更高 | | 元素扩展性 | 新增元素需修改所有访问者 | 新增元素只需修改 variant 定义和访问者重载 | | 操作扩展性 | 新增访问者子类即可 | 新增重载函数 / 结构体即可 | | 适用场景 | 复杂类层次、运行时动态创建元素 | 元素类型固定、追求性能、轻量场景 | | 类型安全 | 运行时检查,有向下转型风险 | 编译期类型检查,绝对安全 | ## 八、总结 访问者模式的本质是**在数据结构稳定的前提下,将操作的变化隔离出来**,通过双分派实现操作与数据的解耦。 - 如果你维护的是**复杂继承体系、元素类型固定、操作多变**的系统(如编译器、解析器、复杂报表系统),传统访问者模式是经典解决方案; - 如果你使用现代 C++(C++17 及以上),且元素类型是一组固定的结构体,**`std::variant + std::visit`** **是更轻量、更安全、性能更好的首选**。 使用的核心判断标准:**元素类型是否稳定**。如果元素会频繁新增,访问者模式会成为维护噩梦,不建议使用。