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

相关推荐
YGGP14 分钟前
【结构型模式】代理模式
设计模式
庄小焱5 小时前
设计模式——中介者设计模式(行为型)
设计模式
庄小焱7 小时前
设计模式——备忘录设计模式(行为型)
设计模式
庄小焱8 小时前
设计模式——代理设计模式(结构型)
设计模式
哆啦A梦的口袋呀8 小时前
基于Python学习《Head First设计模式》第三章 装饰者模式
python·学习·设计模式
哆啦A梦的口袋呀8 小时前
基于Python学习《Head First设计模式》第五章 单件模式
python·学习·设计模式
季鸢9 小时前
Java设计模式之备忘录模式详解
java·设计模式·备忘录模式
摘星编程12 小时前
工厂方法模式深度解析:从原理到应用实战
java·设计模式·软件工程·工厂方法模式
何中应13 小时前
【设计模式-4.7】行为型——备忘录模式
java·设计模式·备忘录模式
suixinger_lmh1 天前
功能结构整理
unity·设计模式·c#·源代码管理