C++ 设计模式 - 访问者模式

一:概述

访问者模式将作用于对象层次结构的操作封装为一个对象,并使其能够在不修改对象层次结构的情况下定义新的操作。

《设计模式:可复用面向对象软件的基础》一书中的访问者模式因两个原因而具有传奇色彩:一是因为它的复杂性,二是因为它使用了一种名为"双重分派"的技术。双重分派指的是根据对象和函数参数选择成员函数的过程。当然,访问者模式的复杂性主要在于 C++ 本身不支持双重分派。在讨论单分派和双重分派之前,让我先谈谈访问者模式。

什么是"双重分派"?双重分派(Double Dispatch)是一个动态选择调用方法的机制,其中被调用的具体函数依赖于两个对象的运行时类型,而不仅仅是一个对象的类型。这在多态编程中尤其有用,能够实现复杂的行为决策。在 C++ 中,双重分派通常通过虚函数和函数重载的组合实现,例如在访问者模式中。

二:使用场景

  1. 操作应该在对象层次结构上执行。

  2. 操作变更频繁。

  3. 对象层次结构是稳定的。

三:类结构设计

  • Visitor 类 :在对象结构上定义访问操作(visit 方法),用来描述对象结构中各元素可能执行的行为。

  • ConcreteVisitor 类:实现 Visitor 接口中的具体访问操作,为不同的元素提供特定的处理逻辑。

  • Element 类 :表示对象结构中的元素,定义了 accept 方法,用于接收 Visitor 的操作请求。

  • ConcreteElement 类 :具体的对象结构元素,实现 accept 方法,将自己作为参数传递给访问者,以完成双向的操作绑定。

四:一个具体的例子

访问者模式有两种类型的层次结构:对象层次结构(CarElement)和操作层次结构(CarElementVisitor)。对象层次结构相对稳定,但操作层次结构可能需要支持新操作。CarElement 和 CarElementVisitor 都充当接口,这意味着每个具体的汽车元素(如 Wheel、Engine、Body 和 Car)都必须实现 accept(CarElementVisitor) 成员函数。相应地,每个具体的操作(如 CarElementDoVisitor 和 CarElementPrintVisitor)都必须实现四个重载的 visit(Wheel)visit(Engine)visit(Body)visit(Car) 方法。

假设操作 CarElementPrintVisitor 被应用于对象层次结构。CarElementPrintVisitor 的任务可能是打印被访问的汽车零部件的名称。首先,像 Engine 这样的汽车元素接受访问者(accept(CarElementVisitor)),并使用访问者通过 visitor.visit(this) 调用操作层次结构,将自身作为参数传递。这确保了调用 CarElementPrintVisitor 上的 visit(Engine) 重载。访问 Car 是特殊的,因为 Car 由多个汽车元素组成。因此,Caraccept 成员函数将接受调用委托给它的所有汽车零部件。

关于访问者的一个关键特征是:它依赖于两个对象,决定执行什么操作:访问者和被访问的对象。

五:代码示例

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>

// 前向声明 Visitor 和元素类
class CarElementVisitor;

class CarElement {
public:
    // 接受访问者操作的接口,由具体元素实现
    virtual void accept(CarElementVisitor& visitor) const = 0;
    virtual ~CarElement() = default;
};

// 前向声明具体元素类
class Body;
class Car;
class Engine;
class Wheel;

// 访问者接口,定义了针对不同元素的访问操作
class CarElementVisitor {
public:
    // 针对不同具体元素的访问操作的虚函数
    virtual void visit(Body body) const = 0;
    virtual void visit(Car car) const = 0;
    virtual void visit(Engine engine) const = 0;
    virtual void visit(Wheel wheel) const = 0;
    virtual ~CarElementVisitor() = default;
};

// 轮胎类,表示汽车的一个轮胎
class Wheel : public CarElement {
public:
    // 构造函数,接收轮胎的名称
    Wheel(const std::string& n) : name(n) { }

    // 接受访问者,调用访问者的 visit(Wheel) 方法
    void accept(CarElementVisitor& visitor) const override {
        visitor.visit(*this);
    }

    // 获取轮胎的名称
    std::string getName() const {
        return name;
    }
private:
    std::string name; // 轮胎名称
};

// 车身类,表示汽车的车身
class Body : public CarElement {
public:
    // 接受访问者,调用访问者的 visit(Body) 方法
    void accept(CarElementVisitor& visitor) const override {
        visitor.visit(*this);
    }
};

// 发动机类,表示汽车的发动机
class Engine : public CarElement {
public:
    // 接受访问者,调用访问者的 visit(Engine) 方法
    void accept(CarElementVisitor& visitor) const override {
        visitor.visit(*this);
    }
};

// 汽车类,表示一辆汽车,由多个汽车元素组成
class Car : public CarElement {
public:
    // 构造函数,接收一个汽车元素列表
    Car(std::initializer_list<CarElement*> carElements) : elements{ carElements } {}

    // 接受访问者,依次让每个汽车元素接受访问
    void accept(CarElementVisitor& visitor) const override {
        for (auto elem : elements) {
            elem->accept(visitor); // 委托访问操作给子元素
        }
        visitor.visit(*this); // 最后访问汽车本身
    }
private:
    std::vector<CarElement*> elements; // 汽车元素列表
};

// 执行动作的访问者类,定义具体的访问行为
class CarElementDoVisitor : public CarE

六:相关设计模式

  • 稳定的对象层次结构通常会应用 组合模式(Composite Pattern)
  • 迭代器模式(Iterator Pattern) 通常用于遍历对象层次结构。
  • 新的元素可以通过创建型模式(例如 工厂方法(Factory Method)原型模式(Prototype Pattern))来创建

七:优缺点

优点:

  • 可以轻松地向操作层次结构中添加新的操作(访问者)。
  • 一个操作可以被封装在一个访问者中。
  • 在遍历对象层次结构时,可以构建和维护状态。

缺点:

  • 如果需要在对象层次结构中添加新的被访问对象(VisitedObject),修改会非常困难。
  • 需要将新的被访问对象(VisitedObject)添加或从对象层次结构中移除。
  • 需要扩展访问者的接口,并在每个具体访问者中添加或移除 visit(VisitedObject) 成员函数。

八:参考:

  1. The Visitor Pattern -- MC++ BLOG

  2. https://commons.wikimedia.org/w/index.php?curid=122709059

相关推荐
一个处女座的程序猿O(∩_∩)O17 小时前
React 设计模式:实用指南
前端·react.js·设计模式
扫地僧00917 小时前
第17章 读写锁分离设计模式(Java高并发编程详解:多线程与系统设计)
java·前端·设计模式
C1829818257517 小时前
常用设计模式
设计模式
扫地僧00917 小时前
第19章 Future设计模式(Java高并发编程详解:多线程与系统设计)
java·python·设计模式
LUCIAZZZ1 天前
通过Demo案例的形式弄懂Java中的设计模式
java·开发语言·spring boot·spring·设计模式·代理模式
FLZJ_KL1 天前
【设计模式】【行为型模式】策略模式(Strategy)
java·设计模式·策略模式·java基础
sniper_fandc1 天前
责任链模式
java·设计模式·责任链模式
天堂的恶魔9461 天前
C++设计模式 —— 工厂模式
javascript·c++·设计模式
天堂的恶魔9461 天前
C++设计模式 —— 单例模式
c++·单例模式·设计模式