设计模式——访问者模式

一、访问者模式核心概念

访问者模式(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. 适用场景是 "数据结构固定、操作频繁变化",缺点是新增元素类型时成本高,需权衡使用。
相关推荐
wangchunting11 小时前
Java设计模式
java·单例模式·设计模式
孟陬1 天前
国外技术周刊 #3:“最差程序员”带动高效团队、不写代码的创业导师如何毁掉创新…
前端·后端·设计模式
砍光二叉树1 天前
【设计模式】结构型-代理模式
设计模式·系统安全·代理模式
新缸中之脑1 天前
AI智能体五大设计模式
人工智能·机器学习·设计模式
砍光二叉树1 天前
【设计模式】结构型-装饰器模式
设计模式·装饰器模式
han_1 天前
JavaScript设计模式(三):代理模式实现与应用
前端·javascript·设计模式
我的offer在哪里1 天前
POM 设计模式深度解析|博客视角:从原理到落地,让自动化测试脚本 “活” 起来
设计模式
程序员Terry1 天前
Java 代理模式:从生活中的"中介"到代码中的"代理人"
后端·设计模式
砍光二叉树1 天前
【设计模式】结构型-适配器模式
设计模式·适配器模式
Yu_Lijing1 天前
基于C++的《Head First设计模式》笔记——蝇量模式
c++·笔记·设计模式