一、访问者模式核心概念访问者模式Visitor Pattern是一种行为型设计模式它的核心思想是将数据结构与对数据的操作分离。简单来说就是当你有一组固定的对象结构但需要频繁新增不同的操作逻辑时不需要修改这些对象本身而是把操作逻辑封装成 “访问者”让对象接受访问者的 “访问” 并执行对应的操作。可以用一个比喻理解博物馆里的展品固定的数据结构不会变但不同的参观者访问者会对展品做不同的操作游客只是看、研究员记录信息、保洁员清洁展品只需要提供一个 “接受访问” 的接口具体操作由参观者决定。核心角色抽象元素Element定义一个接受访问者的接口通常是accept(Visitor)方法。具体元素ConcreteElement实现抽象元素的接口调用访问者的对应方法。抽象访问者Visitor为每个具体元素定义一个访问方法visit(ConcreteElement)。具体访问者ConcreteVisitor实现抽象访问者的方法定义对具体元素的具体操作。对象结构ObjectStructure管理元素的集合提供遍历元素的接口供访问者遍历访问。二、C 代码示例我们以 “动物园” 为场景动物园里有老虎、猴子两种固定的动物数据结构需要新增 “喂食”“体检” 两种操作访问者用访问者模式实现这个需求。#include iostream #include vector #include memory // 前置声明抽象元素需要知道抽象访问者反之亦然 class Visitor; // ---------------------- 1. 抽象元素Element ---------------------- class Animal { public: virtual ~Animal() default; // 核心接口接受访问者的访问 virtual void accept(Visitor visitor) 0; }; // ---------------------- 2. 具体元素ConcreteElement ---------------------- // 老虎 class Tiger : public Animal { public: // 接受访问者调用访问者针对老虎的方法 void accept(Visitor visitor) override { visitor.visit(*this); // 把自身传递给访问者 } // 老虎的特有属性/方法 void roar() const { std::cout 老虎嗷呜 std::endl; } std::string getName() const { return 老虎; } }; // 猴子 class Monkey : public Animal { public: void accept(Visitor visitor) override { visitor.visit(*this); } // 猴子的特有属性/方法 void jump() const { std::cout 猴子蹦蹦跳跳 std::endl; } std::string getName() const { return 猴子; } }; // ---------------------- 3. 抽象访问者Visitor ---------------------- class Visitor { public: virtual ~Visitor() default; // 为每个具体元素定义访问方法 virtual void visit(Tiger tiger) 0; virtual void visit(Monkey monkey) 0; }; // ---------------------- 4. 具体访问者ConcreteVisitor ---------------------- // 喂食访问者 class FeedVisitor : public Visitor { public: void visit(Tiger tiger) override { std::cout 给 tiger.getName() 喂肉 std::endl; tiger.roar(); // 可以调用元素的特有方法 } void visit(Monkey monkey) override { std::cout 给 monkey.getName() 喂香蕉 std::endl; monkey.jump(); } }; // 体检访问者 class CheckVisitor : public Visitor { public: void visit(Tiger tiger) override { std::cout 给 tiger.getName() 检查牙齿和爪子 std::endl; } void visit(Monkey monkey) override { std::cout 给 monkey.getName() 检查尾巴和视力 std::endl; } }; // ---------------------- 5. 对象结构ObjectStructure ---------------------- class Zoo { private: std::vectorstd::unique_ptrAnimal animals; // 管理元素集合 public: // 添加动物 void addAnimal(std::unique_ptrAnimal animal) { animals.push_back(std::move(animal)); } // 让访问者遍历所有动物并执行操作 void accept(Visitor visitor) { for (auto animal : animals) { animal-accept(visitor); } } }; // ---------------------- 测试代码 ---------------------- int main() { // 1. 创建动物园并添加动物 Zoo zoo; zoo.addAnimal(std::make_uniqueTiger()); zoo.addAnimal(std::make_uniqueMonkey()); // 2. 喂食操作 std::cout 执行喂食操作 std::endl; FeedVisitor feedVisitor; zoo.accept(feedVisitor); // 3. 体检操作 std::cout \n 执行体检操作 std::endl; CheckVisitor checkVisitor; zoo.accept(checkVisitor); return 0; }代码运行结果 执行喂食操作 给老虎喂肉 老虎嗷呜 给猴子喂香蕉 猴子蹦蹦跳跳 执行体检操作 给老虎检查牙齿和爪子 给猴子检查尾巴和视力代码关键解释核心接口accept动物类Element的accept方法接收一个访问者对象并调用访问者的visit方法同时把自身作为参数传递。这一步是 “双重分派”先根据动物类型Tiger/Monkey调用对应的accept再根据访问者类型Feed/Check调用对应的visit最终确定执行哪个操作。新增操作的扩展如果需要新增 “打扫笼子” 操作只需要新增一个CleanVisitor类实现Visitor接口无需修改任何动物类和现有访问者类符合 “开闭原则”。对象结构Zoo封装了元素的集合管理和遍历逻辑让客户端只需调用zoo.accept(visitor)就能批量执行操作简化了客户端代码。#include iostream #include vector #include memory #include cmath // 前置声明抽象访问者 class ShapeVisitor; // ---------------------- 1. 抽象元素Shape ---------------------- class Shape { public: virtual ~Shape() default; // 核心接受访问者的接口 virtual void accept(ShapeVisitor visitor) 0; }; // ---------------------- 2. 具体元素Circle, Rectangle ---------------------- class Circle : public Shape { private: double radius_; public: explicit Circle(double radius) : radius_(radius) {} void accept(ShapeVisitor visitor) override; double getRadius() const { return radius_; } }; class Rectangle : public Shape { private: double width_; double height_; public: Rectangle(double width, double height) : width_(width), height_(height) {} void accept(ShapeVisitor visitor) override; double getWidth() const { return width_; } double getHeight() const { return height_; } }; // ---------------------- 3. 抽象访问者ShapeVisitor ---------------------- class ShapeVisitor { public: virtual ~ShapeVisitor() default; // 为每种具体图形声明访问方法 virtual void visit(Circle circle) 0; virtual void visit(Rectangle rectangle) 0; }; // ---------------------- 4. 具体访问者AreaCalculator ---------------------- class AreaCalculator : public ShapeVisitor { private: double totalArea_ 0.0; // 访问者可累积状态如总面积 public: void visit(Circle circle) override { double area M_PI * circle.getRadius() * circle.getRadius(); std::cout 圆形面积: area std::endl; totalArea_ area; } void visit(Rectangle rectangle) override { double area rectangle.getWidth() * rectangle.getHeight(); std::cout 矩形面积: area std::endl; totalArea_ area; } double getTotalArea() const { return totalArea_; } }; // 具体元素的accept方法实现需在ShapeVisitor声明后定义 void Circle::accept(ShapeVisitor visitor) { visitor.visit(*this); // 将自身Circle类型传递给访问者 } void Rectangle::accept(ShapeVisitor visitor) { visitor.visit(*this); // 将自身Rectangle类型传递给访问者 } // ---------------------- 5. 对象结构ShapeCollection ---------------------- class ShapeCollection { private: std::vectorstd::unique_ptrShape shapes_; public: void addShape(std::unique_ptrShape shape) { shapes_.push_back(std::move(shape)); } // 允许访问者遍历所有图形 void accept(ShapeVisitor visitor) { for (auto shape : shapes_) { shape-accept(visitor); // 触发双重分派 } } }; // ---------------------- 客户端测试代码 ---------------------- int main() { // 1. 创建图形集合 ShapeCollection collection; collection.addShape(std::make_uniqueCircle(5.0)); // 圆形半径5 collection.addShape(std::make_uniqueRectangle(4.0, 6.0)); // 矩形宽4高6 collection.addShape(std::make_uniqueCircle(2.0)); // 圆形半径2 // 2. 创建面积计算访问者 AreaCalculator areaCalc; // 3. 执行面积计算操作 std::cout 计算各图形面积 std::endl; collection.accept(areaCalc); // 4. 输出累计总面积访问者内部状态的体现 std::cout \n所有图形总面积: areaCalc.getTotalArea() std::endl; return 0; }计算各图形面积 圆形面积: 78.5398 矩形面积: 24 圆形面积: 12.5664 所有图形总面积: 115.1062三、访问者模式的适用场景数据结构稳定如示例中的动物类型固定但需要频繁新增不同的操作逻辑。需要对一组对象进行多种不相关的操作且不想让这些操作污染对象本身。需要集中管理一组对象的某类操作如所有动物的体检逻辑都在CheckVisitor中便于维护。四、访问者模式的优缺点表格优点缺点操作与数据结构分离新增操作只需加访问者符合开闭原则新增元素类型时需要修改所有访问者的接口违反开闭原则集中管理同类操作代码可读性、维护性更高访问者可能需要访问元素的私有属性破坏封装性可通过友元 / 提供接口解决可以在访问者中积累状态如统计所有动物的喂食总量双重分派逻辑增加了代码复杂度新手不易理解总结访问者模式的核心是分离数据结构和操作通过 “接受 - 访问” 的双重分派机制实现对不同元素的差异化操作。C 实现的关键是抽象元素定义accept接口抽象访问者为每个具体元素定义visit接口具体元素调用访问者的对应visit方法。适用场景是 “数据结构固定、操作频繁变化”缺点是新增元素类型时成本高需权衡使用。