突破编程_C++_设计模式(访问者模式)

1 访问者模式的基本概念

C++中的访问者模式是一种行为设计模式,它允许你在不修改类层次结构的情况下增加新的操作。这种模式将数据结构与数据操作解耦,使得操作可以独立于对象的类来定义。

访问者模式的主要组成部分包括:

(1)访问者(Visitor): 这是一个接口,它声明了一个访问操作,该操作可以被应用到所有的元素上。访问操作的具体实现在访问者的子类中完成。

(2)元素(Element): 这是一个接口,它定义了一个接受操作,该操作可以被一个访问者访问。元素接口通常有一个接受访问者作为参数的接受方法。

(3)具体元素(Concrete Element): 这是实现元素接口的具体类,它实现了接受方法,该方法将调用访问者的访问方法。

(4)对象结构(Object Structure): 这是元素的集合,它以一种合适的方式包含元素。对象结构可以是一个简单的集合,也可以是一个复杂的树形结构。

2 访问者模式的实现步骤

访问者模式的实现步骤如下:

(1)定义访问者接口:

首先,需要定义一个访问者接口,这个接口声明了所有可能应用于元素对象的操作。这些操作通常作为纯虚函数存在,具体的操作实现将由具体的访问者类来完成。

(2)定义元素接口:

接下来,定义一个元素接口,这个接口声明了一个接受方法,该方法接受一个访问者对象作为参数。元素接口中的接受方法通常是虚函数,以便在元素的具体类中实现多态性。

(3)实现具体元素类:

然后,创建实现元素接口的具体元素类。这些类将包含数据,并实现接受方法,该方法将调用访问者的访问方法。

(4)实现具体的访问者类:

接着,创建实现访问者接口的具体访问者类。这些类将包含访问元素所需的具体逻辑。当访问者的访问方法被调用时,它将针对具体的元素对象执行相应的操作。

(5)创建对象结构:

创建一个对象结构,该结构负责管理元素对象的集合。这可以是一个简单的容器,如数组或向量,也可以是一个更复杂的结构,如树或图。

(6)使用访问者模式:

最后,在客户端代码中,创建具体的访问者对象,并通过对象结构访问元素对象。当需要执行某个操作时,通过对象结构的遍历方法,将具体的访问者对象传递给每个元素对象的接受方法。元素对象的接受方法将调用访问者的访问方法,从而执行相应的操作。

如下为样例代码:

#include <iostream>  
#include <vector>  
#include <memory>  class Element;// 步骤一:定义访问者接口  
class Visitor {
public:virtual ~Visitor() = default;virtual void visit(const std::shared_ptr<Element>& element) const = 0;
};// 步骤二:定义元素接口  
class Element : public std::enable_shared_from_this<Element> {
public:virtual ~Element() = default;virtual void accept(const std::shared_ptr<Visitor>& visitor) = 0;
};// 步骤三:实现具体元素类  
class ConcreteElement : public Element {
public:void accept(const std::shared_ptr<Visitor>& visitor) override {visitor->visit(shared_from_this());}void operation() const {std::cout << "ConcreteElement operation" << std::endl;}
};// 步骤四:实现具体的访问者类  
class ConcreteVisitor : public Visitor {
public:void visit(const std::shared_ptr<Element>& element) const override {auto concreteElement = std::dynamic_pointer_cast<ConcreteElement>(element);if (concreteElement) {concreteElement->operation();std::cout << "Visited by ConcreteVisitor" << std::endl;}else {std::cout << "Element is not of type ConcreteElement" << std::endl;}}
};// 步骤五:创建对象结构  
class ObjectStructure {
public:void attach(const std::shared_ptr<Element>& element) {m_elements.push_back(element);}void detach(const std::shared_ptr<Element>& element) {auto it = std::find(m_elements.begin(), m_elements.end(), element);if (it != m_elements.end()) {m_elements.erase(it);}}void accept(const std::shared_ptr<Visitor>& visitor) const {for (const auto& element : m_elements) {element->accept(visitor);}}private:std::vector<std::shared_ptr<Element>> m_elements;
};// 步骤六:使用访问者模式  
int main() 
{// 创建对象结构并添加元素  ObjectStructure objectStructure;objectStructure.attach(std::make_shared<ConcreteElement>());// 创建访问者  std::shared_ptr<Visitor> visitor = std::make_shared<ConcreteVisitor>();// 执行访问  objectStructure.accept(visitor);return 0;
}

上面代码的输出为:

ConcreteElement operation
Visited by ConcreteVisitor

上面的代码使用了 std::shared_ptr 智能指针来管理 Visitor 和 Element 对象的生命周期。std::dynamic_pointer_cast 用于安全地执行动态类型转换,以确认 Element 指针是否可以转换为 ConcreteElement 类型。如果转换成功,则执行相应的操作。

ObjectStructure 类负责管理 Element 对象的集合,并提供 attach 和 detach 方法来添加和删除元素。accept 方法则遍历所有元素,并将访问者传递给每个元素的 accept 方法,从而执行访问操作。

在 main 函数中,创建了一个 ObjectStructure 对象,并向其添加了一个 ConcreteElement 对象。然后,创建了一个 ConcreteVisitor 对象,并通过 ObjectStructure 的 accept 方法将访问者传递给所有元素。这样,就实现了访问者模式的使用。

3 访问者模式的应用场景

C++ 访问者模式的应用场景广泛,主要适用于以下情况:

(1)双层分派场景: 访问者模式实现了一种称为“双层分派”的机制。第一层分派发生在客户端代码中,当客户端将访问者对象传递给元素对象的 accept 方法时。第二层分派发生在元素对象的 accept 方法内部,它根据访问者的类型调用相应的访问方法。这种双层分派机制使得操作可以在元素对象的上下文中执行,并且可以根据元素的类型进行不同的处理。

(2)数据结构稳定但操作易变的场景: 如果数据结构相对稳定,但需要在其上执行的操作经常变化,访问者模式可以帮助将这些操作与数据结构解耦,使得操作的变化不会影响到数据结构的定义。

(3)操作需要扩展的场景: 当需要对一个对象结构中的元素执行多种不同的、不相关的操作时,且这些操作可能会在未来发生变化或增加时,使用访问者模式非常合适。通过将操作封装在访问者中,可以在不修改元素类的情况下添加新的操作。

(4)需要对对象结构中的对象进行不同操作的场景: 当需要在运行时决定对对象结构中的哪些元素执行哪些操作时,访问者模式可以很容易地实现这种动态行为。

(5)复杂的对象结构遍历: 当需要遍历一个复杂的对象结构(如树形结构、图结构等),并对结构中的每个元素执行特定的操作时,访问者模式可以简化代码结构,使得遍历和操作逻辑更加清晰和分离。

3.1 访问者模式应用于双层分派场景

双层分派允许根据对象类型和访问者类型动态地决定执行哪个操作。以下是一个简单的游戏场景,其中游戏角色可以被不同的访问者(例如渲染器或AI处理器)访问。

首先,定义访问者接口和元素接口:

#include <iostream>  
#include <memory>  
#include <vector>  
#include <string>  class Character;// 访问者接口  
class Visitor {
public:virtual ~Visitor() = default;virtual void visit(const std::shared_ptr<Character>& character) const = 0;
};// 元素接口  
class Character : public std::enable_shared_from_this<Character> {
public:virtual ~Character() = default;virtual void accept(const std::shared_ptr<Visitor>& visitor) = 0;virtual std::string getName() const = 0;
};

接着,定义具体的游戏角色类和访问者类:

// 具体游戏角色类A  
class Warrior : public Character {
public:std::string getName() const override { return "Warrior"; }void accept(const std::shared_ptr<Visitor>& visitor) override {visitor->visit(shared_from_this());}void fight() const {std::cout << getName() << " is fighting!" << std::endl;}
};// 具体游戏角色类B  
class Mage : public Character {
public:std::string getName() const override { return "Mage"; }void accept(const std::shared_ptr<Visitor>& visitor) override {visitor->visit(shared_from_this());}void castSpell() const {std::cout << getName() << " is casting a spell!" << std::endl;}
};// 具体访问者类:渲染器  
class Renderer : public Visitor {
public:void visit(const std::shared_ptr<Character>& character) const override {std::cout << "Rendering " << character->getName() << std::endl;}
};// 具体访问者类:AI处理器  
class AIProcessor : public Visitor {
public:void visit(const std::shared_ptr<Character>& character) const override {std::cout << "Processing AI for " << character->getName() << std::endl;// 这里可以根据角色类型执行不同的AI逻辑  }
};

最后,定义一个管理游戏角色的对象结构类,并使用访问者进行双层分派:

// 对象结构类:游戏世界  
class GameWorld {
public:void addCharacter(const std::shared_ptr<Character>& character) {m_characters.push_back(character);}void render(const std::shared_ptr<Visitor>& visitor) const {for (const auto& character : m_characters) {character->accept(visitor);}}private:std::vector<std::shared_ptr<Character>> m_characters;
};int main() 
{// 创建游戏角色  auto warrior = std::make_shared<Warrior>();auto mage = std::make_shared<Mage>();// 创建游戏世界并添加角色  GameWorld gameWorld;gameWorld.addCharacter(warrior);gameWorld.addCharacter(mage);// 创建渲染器访问者  auto renderer = std::make_shared<Renderer>();// 使用渲染器访问者渲染所有角色  gameWorld.render(renderer);// 创建AI处理器访问者  auto aiProcessor = std::make_shared<AIProcessor>();// 使用AI处理器访问者处理所有角色的AI  gameWorld.render(aiProcessor);// 不需要手动释放对象,智能指针会自动处理  return 0;
}

上面代码的输出为:

Rendering Warrior
Rendering Mage
Processing AI for Warrior
Processing AI for Mage

在这个示例中,Warrior 和 Mage 类继承了 Character 接口,并实现了各自的 accept 方法和特定行为(如 fight 和 castSpell)。Renderer 和 AIProcessor 类继承了 Visitor 接口,并实现了针对 Character 的 visit 方法。

GameWorld 类管理着游戏中的角色,并提供了 render 方法,该方法接受一个 Visitor 智能指针,并使用 accept 方法触发双层分派。当调用 gameWorld.render(renderer) 时,所有角色都会被渲染器访问,并调用它们各自的 accept 方法。由于 accept 方法内部调用了 visitor->visit(shared_from_this()),这就会触发对应的 visit 方法,在这里是 Renderer 类的 visit 方法,负责渲染角色。

3.2 访问者模式应用数据结构稳定但操作易变的场景

该场景是指:对于一组稳定的数据结构(元素),在这些数据结构上执行各种易变的操作(访问者)时,每个操作可能需要访问数据结构的不同部分,并且可能以不同的方式处理这些部分。

下面是一个使用访问者模式的示例,它模拟了一个简单的形状集合,其中形状是稳定的数据结构,而不同的操作(如计算面积、周长等)是易变的访问者。

首先,定义形状接口和访问者接口:

#include <iostream>  
#include <memory>  
#include <vector>  class Visitor;// 形状接口  
class Shape : public std::enable_shared_from_this<Shape> {
public:virtual ~Shape() = default;virtual void accept(const std::shared_ptr<Visitor>& visitor) = 0;
};class Circle;
class Rectangle;// 访问者接口  
class Visitor {
public:virtual ~Visitor() = default;virtual void visit(const std::shared_ptr<Circle>& circle) const = 0;virtual void visit(const std::shared_ptr<Rectangle>& rectangle) const = 0;
};

接着,定义具体的形状类和访问者类:

// 圆形类  
class Circle : public Shape {double radius;
public:Circle(double r) : radius(r) {}void accept(const std::shared_ptr<Visitor>& visitor) override {visitor->visit(std::dynamic_pointer_cast<Circle>(shared_from_this()));}double getRadius() const { return radius; }
};// 矩形类  
class Rectangle : public Shape {double width, height;
public:Rectangle(double w, double h) : width(w), height(h) {}void accept(const std::shared_ptr<Visitor>& visitor) override {visitor->visit(std::dynamic_pointer_cast<Rectangle>(shared_from_this()));}double getWidth() const { return width; }double getHeight() const { return height; }
};// 计算面积的访问者  
class AreaVisitor : public Visitor {
public:void visit(const std::shared_ptr<Circle>& circle) const override {std::cout << "Circle area: " << 3.14159 * circle->getRadius() * circle->getRadius() << std::endl;}void visit(const std::shared_ptr<Rectangle>& rectangle) const override {std::cout << "Rectangle area: " << rectangle->getWidth() * rectangle->getHeight() << std::endl;}
};// 计算周长的访问者  
class PerimeterVisitor : public Visitor {
public:void visit(const std::shared_ptr<Circle>& circle) const override {std::cout << "Circle perimeter: " << 2 * 3.14159 * circle->getRadius() << std::endl;}void visit(const std::shared_ptr<Rectangle>& rectangle) const override {std::cout << "Rectangle perimeter: " << 2 * (rectangle->getWidth() + rectangle->getHeight()) << std::endl;}
};

最后,创建一个管理形状的对象结构类,并使用访问者进行操作:

// 对象结构类:形状集合  
class ShapeCollection {
public:void addShape(const std::shared_ptr<Shape>& shape) {m_shapes.push_back(shape);}void process(const std::shared_ptr<Visitor>& visitor) const {for (const auto& shape : m_shapes) {shape->accept(visitor);}}private:std::vector<std::shared_ptr<Shape>> m_shapes;
};int main() 
{// 创建形状  auto circle = std::make_shared<Circle>(5.0);auto rectangle = std::make_shared<Rectangle>(4.0, 6.0);// 创建形状集合并添加形状  ShapeCollection collection;collection.addShape(circle);collection.addShape(rectangle);// 创建并使用面积访问者  auto areaVisitor = std::make_shared<AreaVisitor>();collection.process(areaVisitor);// 创建并使用周长访问者  auto perimeterVisitor = std::make_shared<PerimeterVisitor>();collection.process(perimeterVisitor);// 不需要手动释放对象,智能指针会自动处理  return 0;
}

上面代码的输出为:

Circle area: 78.5397
Rectangle area: 24
Circle perimeter: 31.4159
Rectangle perimeter: 20

在这个示例中,Circle 和 Rectangle 类继承了 Shape 接口,并实现了各自的 accept 方法和特定行为(半径以及长宽)。AreaVisitor 和 PerimeterVisitor 类继承了 Visitor 接口,并实现了计算圆形面积周长以及计算矩形面积周长的 visit 方法。随后在 ShapeCollection 的 process 进行访问调用。

4 访问者模式的优点与缺点

C++ 访问者模式的优点主要包括:

(1)扩展性好: 访问者模式使得在不修改已有类的情况下,可以添加新的操作。这意味着数据结构本身保持稳定,而新的操作可以很容易地添加到系统中。

(2)操作集中管理: 访问者模式将相关的操作集中到一个访问者类中,这有助于操作的封装和复用。

(3)双分派: 访问者模式通过对象的“接受”方法和访问者的“访问”方法实现了双分派,即运行时根据对象的实际类型和访问者的类型来确定执行的操作。

(4)灵活性: 访问者模式允许我们根据需要灵活地定义不同的访问者类,实现不同的操作逻辑。

(5)符合开闭原则: 访问者模式符合开闭原则,即对扩展开放,对修改封闭。当需要添加新的操作时,我们只需要添加新的访问者类,而不需要修改已有的数据结构。

然而,C++ 访问者模式也存在一些缺点:

(1)增加复杂性: 访问者模式增加了类的数量,使得系统变得更加复杂。每个元素类都需要实现接受方法,每个访问者类都需要实现访问方法,这可能导致代码量的增加。

(2)破坏封装性: 访问者模式要求元素类暴露其内部状态给访问者类,这可能会破坏元素的封装性。如果访问者类不恰当地使用这些内部状态,可能会导致错误或不可预见的行为。

(3)类型安全: 在访问者模式中,由于访问者类需要访问元素类的内部状态,因此需要确保类型安全。否则,可能会导致运行时错误。

(4)性能开销: 由于访问者模式涉及到双分派,因此在某些情况下可能会引入额外的性能开销。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757719.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试算法-62-盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。…

CycleGAN训练及测试过程细节记录

CycleGAN训练及测试过程细节记录 文章目录 关于训练关于测试 关于训练 1、训练前将数据配置好&#xff0c;并在Pycharm中写好配置信息 2、关于训练过程的参数配置在 options/train_options.py options/base_options.py batch_size&#xff1a;批大小 crop_size&#xff1a;…

python实现--拓扑排序

拓扑排序是对有向无环图&#xff08;DAG&#xff09;进行排序的一种算法&#xff0c;它可以将图中的顶点排成一个线性序列&#xff0c;使得图中的任意一条有向边都从序列中的较早顶点指向较晚顶点。换句话说&#xff0c;如果图中存在一条从顶点A到顶点B的有向边&#xff0c;那么…

Android分区存储到底该怎么做

文章目录 一、Android存储结构二、什么是分区存储&#xff1f;三、私有目录和公有目录三、存储权限和分区存储有什么关系&#xff1f;四、我们应该该怎么做适配&#xff1f;4.1、利用File进行操作4.2、使用MediaStore操作数据库 一、Android存储结构 Android存储分为内部存储和…

【逆向】使用 Frida 进行 Android 应用程序动态分析与加密算法逆向

不愿染是与非 怎料事与愿违 心中的花枯萎 时光它去不回 回忆辗转来回 痛不过这心扉 愿只愿余生无悔 随花香远飞 &#x1f3b5; 毛不易《不染》 在移动应用程序开发中&#xff0c;保护用户数据的安全至关重要。加密算法是保护数据安全的重要手段之一。然而…

【晴问算法】提高篇—动态规划专题—最长上升子序列

题目描述 现有一个整数序列a1,a2,...,an​​​​​​&#xff0c;求最长的子序列&#xff08;可以不连续&#xff09;&#xff0c;使得这个子序列中的元素是非递减的。输出该最大长度。 输入描述 第一行一个正整数n&#xff08;1≤n≤100​​​​&#xff09;&#xff0c;表示序…

【进阶版讲解深度学习如何入门?】

深度学习如何入门&#xff1f; 1. 前言2. 学习基础知识3. 了解机器学习4. 编程和工具5. 深度学习基础6. 实战项目7. 高级概念8. 持续学习9. 推荐资源 1. 前言 深度学习是机器学习的一个子领域&#xff0c;它受到了生物神经网络的启发&#xff0c;依赖于构建多层的神经网络来学…

Windows 11 安装 Scoop

[Windows 11 安装 Scoop](Windows 11 安装 Scoop) 0. 引言 Scoop 从命令行安装您熟悉和喜爱的程序&#xff0c;差异最小。 它的主要功能如下&#xff1a; 消除权限弹出窗口 隐藏 GUI 向导样式的安装程序 防止PATH污染安装大量程序 避免安装和卸载程序的意外副作用 自动查…

算法-背包问题

问题描述 假设我有一个背包&#xff0c;希望在装得下的情况下&#xff0c;尽量装进价值更多的物品。那么我该怎么做呢&#xff1f; 问题抽象 假设背包的容量是m&#xff0c;就假设是4吧 # 表示背包容量4KG m 4 可选装进背包的物品有n个&#xff0c;物品的价值存储在prices…

支付宝手机网站支付,微信扫描二维码支付

支付宝手机网站支付 支付宝文档 响应示例 <form name"punchout_form" method"post" action"https://openapi.alipay.com/gateway.do?charsetUTF-8&methodalipay.trade.wap.pay&formatjson&signERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE…

Maven打包时报错:Cannot allocate memory

使用Jenkins执行Maven打包任务时报错 Cannot allocate memory解决办法&#xff1a; 配置系统变量 MAVEN_OPTS-Xmx256m -XX:MaxPermSize512m或者 在项目目录下新建文件 .mvn/jvm.config -Xmx256m -Xms256m -XX:MaxPermSize512m -Djava.awt.headlesstrue参考 Jenkins Maven …

MySQL 数据库设计范式

第一范式&#xff08;1NF&#xff09; 每一列都是不可分割的原子数据项第二范式&#xff08;2NF&#xff09; 在1NF的基础上&#xff0c;非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖) 1.函数依赖A->B&#xff0c;如果通过A属性(属性组)的值…

Transformer学习【从零理解】

Transformer 一、整体框架 二、Encoder 1.输入部分: &#xff08;1&#xff09;Embedding&#xff1a;将输入的词转换为对应的词向量。 &#xff08;2&#xff09;位置编码&#xff1a;因为保证输出时&#xff0c;顺序不会打乱&#xff0c;所以要加入时序信息即位置编码。 公…

如何避免AI网红经济泡沫?警惕细分行业的AI转型而不是转行

一、AI泡沫预防针 要避免AI相关新概念催生的网红经济泡沫&#xff0c;可以从多个角度采取措施&#xff1a; 1. **理性投资**&#xff1a; - 投资者应对AI项目和网红经济中的企业进行深入研究&#xff0c;了解其真实的技术实力、商业模式的可行性和盈利能力&#xff0c;而非…

代码随想录Day52:最长递增子序列、最长连续递增序列、最长重复子数组

最长递增子序列 class Solution { public:int lengthOfLIS(vector<int>& nums) {if(nums.size() < 1) return nums.size();vector<int> dp(nums.size(), 1);int res 0;for(int i 1; i < nums.size(); i){for(int j 0; j < i; j){if(nums[i] > …

初识GO语言

是由google公司推出的一门编程语言&#xff0c;12年推出的第一个版本 Go的特点 Go为什么能在最近的IT领域炙手可热 集python简洁&C语言的性能于一身 21世纪的C语言 顺应容器化时代的到来 区块链的崛起 学习一门编程语言可以划分为下面这三个步骤 安装 编译器 or 解…

JAVA多线程之synchronized锁

文章目录 1. 临界区2. synchronized使用2.1 不加锁实现2.2 synchronized加锁2.3 面向对象的改进2.4 方法上加synchronized2.5 线程安全 3. Monitor3.1 Java对象头3.2 Monitor工作流程3.3 字节码角度 4. synchronized原理4.1 轻量级锁4.2 锁膨胀4.3 偏向锁4.3.1 偏向锁过程4.3.2…

【链表】Leetcode 2. 两数相加【中等】

两数相加 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c; 并且每个节点只能存储 一位 数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外&#xff0c;这两个数都不…

Redis数据结构对象中的对象共享、对象的空转时长

对象共享 概述 除了用于实现引用计数内存回收机制之外&#xff0c;对象的引用计数属性还带有对象共享的作用。 在Redis中&#xff0c;让多个键共享同一个值对象需要执行以下两个步骤: 1.将数据库键的值指针指向一个现有的值对象2.将被共享的值对象的引用计数增一 目前来说…

pytorch 实现线性回归(Pytorch 03)

一 从零实现线性回归 1.1 生成训练数据 原始 计算公式&#xff0c; 我们先使用该公式生成一批数据&#xff0c;然后使用 结果数据去计算 计算 w1, w2 和 b。 %matplotlib inline import random import torch from d2l import torch as d2ldef synthetic_data(w, b, num_ex…