设计模式——访问者模式

一、访问者模式核心概念

访问者模式(Visitor Pattern)是一种行为型设计模式,它的核心思想是:将数据结构与对数据的操作分离。简单来说,就是当你有一组固定的对象结构,但需要频繁新增不同的操作逻辑时,不需要修改这些对象本身,而是把操作逻辑封装成 "访问者",让对象接受访问者的 "访问" 并执行对应的操作。

可以用一个比喻理解:博物馆里的展品(固定的数据结构)不会变,但不同的参观者(访问者)会对展品做不同的操作(游客只是看、研究员记录信息、保洁员清洁),展品只需要提供一个 "接受访问" 的接口,具体操作由参观者决定。

核心角色
  1. 抽象元素(Element) :定义一个接受访问者的接口(通常是 accept(Visitor&) 方法)。
  2. 具体元素(ConcreteElement):实现抽象元素的接口,调用访问者的对应方法。
  3. 抽象访问者(Visitor) :为每个具体元素定义一个访问方法(visit(ConcreteElement&))。
  4. 具体访问者(ConcreteVisitor):实现抽象访问者的方法,定义对具体元素的具体操作。
  5. 对象结构(ObjectStructure):管理元素的集合,提供遍历元素的接口,供访问者遍历访问。

二、C++ 代码示例

我们以 "动物园" 为场景:动物园里有老虎、猴子两种固定的动物(数据结构),需要新增 "喂食""体检" 两种操作(访问者),用访问者模式实现这个需求。

cpp 复制代码
#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::vector<std::unique_ptr<Animal>> animals; // 管理元素集合
public:
    // 添加动物
    void addAnimal(std::unique_ptr<Animal> 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_unique<Tiger>());
    zoo.addAnimal(std::make_unique<Monkey>());

    // 2. 喂食操作
    std::cout << "===== 执行喂食操作 =====" << std::endl;
    FeedVisitor feedVisitor;
    zoo.accept(feedVisitor);

    // 3. 体检操作
    std::cout << "\n===== 执行体检操作 =====" << std::endl;
    CheckVisitor checkVisitor;
    zoo.accept(checkVisitor);

    return 0;
}
代码运行结果
cpp 复制代码
===== 执行喂食操作 =====
给老虎喂肉
老虎:嗷呜~
给猴子喂香蕉
猴子:蹦蹦跳跳~

===== 执行体检操作 =====
给老虎检查牙齿和爪子
给猴子检查尾巴和视力
代码关键解释
  1. 核心接口 accept

    • 动物类(Element)的 accept 方法接收一个访问者对象,并调用访问者的 visit 方法,同时把自身作为参数传递。
    • 这一步是 "双重分派":先根据动物类型(Tiger/Monkey)调用对应的 accept,再根据访问者类型(Feed/Check)调用对应的 visit,最终确定执行哪个操作。
  2. 新增操作的扩展 :如果需要新增 "打扫笼子" 操作,只需要新增一个 CleanVisitor 类实现 Visitor 接口,无需修改任何动物类和现有访问者类,符合 "开闭原则"。

  3. 对象结构 Zoo :封装了元素的集合管理和遍历逻辑,让客户端只需调用 zoo.accept(visitor) 就能批量执行操作,简化了客户端代码。

三、访问者模式的适用场景

  1. 数据结构稳定(如示例中的动物类型固定),但需要频繁新增不同的操作逻辑。
  2. 需要对一组对象进行多种不相关的操作,且不想让这些操作污染对象本身。
  3. 需要集中管理一组对象的某类操作(如所有动物的体检逻辑都在 CheckVisitor 中,便于维护)。

四、访问者模式的优缺点

表格

优点 缺点
操作与数据结构分离,新增操作只需加访问者,符合开闭原则 新增元素类型时,需要修改所有访问者的接口,违反开闭原则
集中管理同类操作,代码可读性、维护性更高 访问者可能需要访问元素的私有属性,破坏封装性(可通过友元 / 提供接口解决)
可以在访问者中积累状态(如统计所有动物的喂食总量) 双重分派逻辑增加了代码复杂度,新手不易理解

总结

  1. 访问者模式的核心是分离数据结构和操作,通过 "接受 - 访问" 的双重分派机制实现对不同元素的差异化操作。
  2. C++ 实现的关键是:抽象元素定义 accept 接口,抽象访问者为每个具体元素定义 visit 接口,具体元素调用访问者的对应 visit 方法。
  3. 适用场景是 "数据结构固定、操作频繁变化",缺点是新增元素类型时成本高,需权衡使用。
相关推荐
Eloudy2 小时前
动态库中不透明数据结构的设计要点总结
数据结构·设计模式
mCell10 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
阿里巴巴淘系技术团队官网博客12 小时前
设计模式Trustworthy Generation:提升RAG信赖度
人工智能·设计模式
Loo国昌20 小时前
SmartArchitect:AI 驱动的设计平台,让想法秒变流程图
人工智能·后端·设计模式·流程图
为美好的生活献上中指21 小时前
java每日精进 02.10【震惊!数据库树形结构设计5大黑科技:从菜鸟到大神,一文让你性能飙升100倍!】
java·开发语言·设计模式
A小码哥1 天前
CRISP 五要素的高效构建策略
设计模式
yangpipi-1 天前
C++并发编程-9. Actor和CSP设计模式
java·c++·设计模式
YigAin1 天前
Unity23种设计模式之 责任链模式
设计模式·责任链模式
金宗汉2 天前
《宇宙递归拓扑学:基于动态范畴与拓扑熵的跨尺度统一场理论》
人工智能·观察者模式·访问者模式·命令模式