实战设计模式之访问者模式

概述

        访问者模式允许我们在不改变类的前提下,向已有类添加新的功能。简单来说,就是将算法与对象的数据结构进行分离的一种方法。在实际应用中,当我们需要对一组对象执行一些操作,而这些操作又需要随着需求的变化而不断变化时,访问者模式就显得尤为重要了。

        电子商务平台的库存管理系统是现实生活中运用访问者模式的一个典型例子。电子商务平台会销售不同种类的商品,比如:书籍、电子产品和服装等。我们需要定期对库存进行不同的统计分析,包括:计算总价值、统计数量等。随着业务的发展,可能会有新的商品类型加入,也可能会有新的统计需求出现。访问者模式允许我们将特定的商品处理逻辑与商品类本身分离,使得添加新的商品类型或处理逻辑变得更为简单和灵活。

基本原理

        访问者模式的核心思想是:将算法与对象结构分离。具体来说,访问者模式通过定义一个操作(通常称为“访问”),这个操作可以在不修改该元素的类的前提下,为每一个具体元素类声明一个该操作。在访问者模式中,我们有两个主要的角色:一个是接受访问的对象集合(即元素),另一个是对这些对象执行操作的访问者。元素知道如何接受访问者,并且会调用访问者的相应方法来完成操作。

        访问者模式主要由以下五个核心组件构成。

        1、访问者。为每一个具体元素声明一个访问操作,表示访问者访问一个元素所要完成的工作。通常情况下,访问者会包含多个Visit方法,每个方法对应一种具体的元素类型。

        2、具体访问者。实现了访问者接口中的每种Visit方法,以完成具体的业务逻辑。

        3、元素。定义了一个接受访问者的接口Accept,其主要作用是让访问者访问自身。同时,Element也是一个抽象类,代表了一组可以被访问的对象。

        4、具体元素。通常是那些需要被访问的对象,实现了Element接口,并提供自身的具体实现。

        5、对象结构。管理元素对象的集合,提供了遍历元素的方法。

        基于上面的核心组件,访问者模式的实现主要有以下五个步骤。

        1、定义元素接口。创建一个抽象类或接口,至少声明了一个Accept方法。该方法用于接收一个访问者对象,并调用访问者的相应Visit方法来执行对当前元素的操作。

        2、创建具体元素类。对于每一个需要被访问的具体元素,创建一个继承自元素接口的类。在每个具体元素类中,实现Accept方法,使其调用传入的访问者对象的对应Visit方法,并传递自身作为参数。

        3、定义访问者接口。创建一个接口,为每种具体访问者类型定义一个Visit方法。这意味着,每当添加一个新的具体访问者类型时,都需要向访问者接口中添加一个新的Visit方法声明。

        4、实现具体访问者类。根据具体的业务逻辑需求,创建实现访问者接口的具体访问者类。在这些类中,实现所有声明的Visit方法,每个方法都包含了针对特定访问者类型的处理逻辑。

        5、创建对象结构。创建一个管理元素集合的对象结构,可以是一个简单的容器,也可以是更复杂的数据结构。此对象结构应提供遍历其内部元素的方法,并能够接受一个访问者对象,依次对其内部的每个元素调用Accept方法。

实战代码

        在下面的实战代码中,我们使用访问者模式模拟了电子商务平台库存管理系统的实现。

        首先,我们定义了一个抽象基类CProduct,它包含商品的基本信息(名称、价格、库存量),并声明了一个纯虚函数Accept用于接收访问者对象。

        接着,我们定义了两个具体的商品类CBook和CElectronics。它们继承自CProduct,并实现了Accept方法。该方法调用传入的访问者的相应Visit方法,以执行针对具体商品类型的特定操作。

        然后,我们定义了一个抽象访问者类CVisitor。其中声明了针对每种商品类型的Visit方法,并通过具体访问者类CStockValueCalculator实现了这些方法,用来计算库存总价值。此外,还有一个管理商品集合的对象结构类CInventory。它负责存储商品实例,并提供了一个PerformCalculations方法遍历所有商品,对每个商品调用其Accept方法,传入具体的访问者对象以执行相应的业务逻辑。

        最后,在main函数中,我们创建了一个CInventory实例。在添加了几种不同类型的商品后,我们使用CStockValueCalculator访问者来计算库存总价值,并最终输出了结果。

#include <iostream>
#include <vector>
#include <string>using namespace std;class CVisitor;// 元素接口
class CProduct
{
public:CProduct(const string& name, double price, int stock) : m_strName(name), m_dbPrice(price), m_nStock(stock) {}virtual ~CProduct() {}virtual void Accept(CVisitor& visitor) = 0;string GetName() const { return m_strName; }double GetPrice() const { return m_dbPrice; }int GetStock() const { return m_nStock; }protected:string m_strName;double m_dbPrice;int m_nStock;
};// 具体元素:书籍
class CBook : public CProduct
{
public:CBook(const string& name, double price, int stock) : CProduct(name, price, stock) {}void Accept(CVisitor& visitor) override;
};// 具体元素:电子产品
class CElectronics : public CProduct
{
public:CElectronics(const string& name, double price, int stock) : CProduct(name, price, stock) {}void Accept(CVisitor& visitor) override;
};// 访问者接口
class CVisitor
{
public:virtual ~CVisitor() {}virtual void Visit(CBook& book) = 0;virtual void Visit(CElectronics& electronics) = 0;
};// 具体访问者:计算库存总价值
class CStockValueCalculator : public CVisitor
{
public:void Visit(CBook& book) override{m_dbTotalValue += book.GetPrice() * book.GetStock();}void Visit(CElectronics& electronics) override{m_dbTotalValue += electronics.GetPrice() * electronics.GetStock();}double GetTotalValue() const { return m_dbTotalValue; }private:double m_dbTotalValue = 0.0;
};void CBook::Accept(CVisitor& visitor)
{visitor.Visit(*this);
}void CElectronics::Accept(CVisitor& visitor)
{visitor.Visit(*this);
}// 对象结构,用于管理商品集合
class CInventory
{
public:~CInventory(){for (CProduct* product : m_vctProduct){delete product;}}void AddProduct(CProduct* product){m_vctProduct.push_back(product);}void PerformCalculations(CVisitor& visitor){for (CProduct* product : m_vctProduct){product->Accept(visitor);}}private:vector<CProduct*> m_vctProduct;
};int main()
{CInventory inventory;inventory.AddProduct(new CBook("Effective C++", 50.0, 10));inventory.AddProduct(new CElectronics("Phone", 1999.99, 20));CStockValueCalculator calculator;inventory.PerformCalculations(calculator);cout << "Total stock value: " << calculator.GetTotalValue() << endl;return 0;
}

总结

        通过将算法从对象结构中分离出来,访问者模式使得每个角色(元素和访问者)都只负责自己的部分。另外,访问者模式使得添加新的操作变得容易,而不需要修改现有的类。只需创建一个新的访问者类来实现所需的操作即可,无需改动已有的元素类。

        但引入访问者模式会增加系统的设计复杂度,特别是当对象结构中有大量不同类型元素时,需要为每种类型定义相应的Visit方法,增加了代码量和理解难度。访问者需要知道所有被访问元素的具体类型和内部表示,这可能会导致访问者与元素之间产生紧密耦合,从而破坏了封装性。

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

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

相关文章

centos7.9使用docker-compose安装kafka

docker-compose配置文件 services:zookeeper:image: confluentinc/cp-zookeeper:7.0.1hostname: zookeepercontainer_name: zookeeperports:- "2181:2181"environment:ZOOKEEPER_CLIENT_PORT: 2181ZOOKEEPER_TICK_TIME: 2000kafka:image: confluentinc/cp-kafka:7.0…

STM32:Modbus通信协议核心解析:关键通信技术

知识点1【 Modbus通信】 1、Modbus的概述 Modbus是OSI模型第七层的应用层报文传输协议 协议&#xff1a;说明有组包和解包的过程 2、通信机制 Modelbus是一个请求/应答协议 通信机制&#xff1a;主机轮询&#xff0c;从机应答的机制。每个从设备有唯一的地址&#xff0c;主…

LeetCode 3362.零数组变换 III:贪心+优先队列+差分数组——清晰题解

【LetMeFly】3362.零数组变换 III&#xff1a;贪心优先队列差分数组——清晰题解 力扣题目链接&#xff1a;https://leetcode.cn/problems/zero-array-transformation-iii/ 给你一个长度为 n 的整数数组 nums 和一个二维数组 queries &#xff0c;其中 queries[i] [li, ri] …

ORM++ 封装实战指南:安全高效的 C++ MySQL 数据库操作

ORM 封装实战指南&#xff1a;安全高效的 C MySQL 数据库操作 一、环境准备 1.1 依赖安装 # Ubuntu/Debian sudo apt-get install libmysqlclient-dev # CentOS sudo yum install mysql-devel# 编译时链接库 (-I 指定头文件路径 -L 指定库路径) g main.cpp -stdc17 -I/usr/i…

JESD204B 协议介绍

一、协议概述 JESD204B是由JEDEC&#xff08;固态技术协会&#xff09;制定的高速串行接口标准&#xff0c;专为模数转换器&#xff08;ADC&#xff09;、数模转换器&#xff08;DAC&#xff09;与逻辑器件&#xff08;如FPGA、ASIC&#xff09;之间的数据传输设计。其核心目标…

yolov8,c++案例汇总

文章目录 引言多目标追踪案例人体姿态估计算法手势姿态估计算法目标分割算法 引言 以下案例,基于c,ncnn,yolov8既可以在windows10/11上部署, 也可以在安卓端部署, 也可以在嵌入式端部署, 服务器端可支持部署封装为DLL,支持c/c#/java端调用 多目标追踪案例 基于yolov8, ncnn,…

运动规划实战案例 | 图解基于状态晶格(State Lattice)的路径规划(附ROS C++/Python仿真)

目录 1 控制采样 vs 状态采样2 State Lattice路径规划2.1 算法流程2.2 Lattice运动基元生成2.3 几何代价函数2.4 运动学约束启发式 3 算法仿真3.1 ROS C仿真3.2 Python仿真 1 控制采样 vs 状态采样 控制采样的技术路线源自经典的运动学建模思想。这种方法将机器人的控制指令空…

BERT框架:自然语言处理的革命性突破

引言 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;2018年Google推出的BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;框架无疑是一场革命。作为基于Transformer架构的双向编码器表示模型&#xff0c;BERT通过预训练学习…

【Fifty Project - D31】

结束了一个超级消耗周末&#xff0c;满安排之健身梅溪湖游泳做饭喝酒羽毛球赛 完全力竭了&#xff0c;久久不能恢复过来&#xff0c;暂停健身安排了 端午后再继续 今日完成记录 TimePlan完成情况7&#xff1a;30 - 8&#xff1a;10有氧爬坡√9&#xff1a;00 - 11&#xff1a;…

信息学奥赛一本通 1547:【 例 1】区间和

【题目链接】 ybt 1547&#xff1a;【 例 1】区间和 【题目考点】 1. 线段树 2. 树状数组 【解题思路】 本题要求维护区间和&#xff0c;实现单点修改、区间查询。 解法1&#xff1a;线段树 线段树原理&#xff0c;及实现方法见&#xff1a;洛谷 P3374 【模板】树状数组…

力扣面试150题--求根节点到叶节点数字之和

Day 48 题目描述 思路 我们利用sum这个全局变量来保存总和值&#xff0c;递归函数sum来计算每个根到叶子节点路径所代表的数&#xff0c;由于我们需要遍历到每条根到叶子节点的路径&#xff0c;所有我采取了前序遍历&#xff0c;如果不是叶子节点&#xff0c;就计算到该节点代…

DJI上云API官方demo学习

1、websocket&#xff0c;所在位置如下图&#xff0c;调用的可以用//websocket搜索 2、用到的http客户端&#xff0c;axios 3、很多和后端交互都是走的http请求

uniapp开发小程序,如何根据权限动态配置按钮或页面内容

前言 写了好几个项目&#xff0c;发现小程序对权限控制非常麻烦&#xff0c;于是有了这个想法&#xff0c;但是网上找了一圈没有一个比较完善的讲解&#xff0c;因为小程序不支持自定义指令&#xff0c;所以不能像后台那样方便&#xff0c;于是就将几个博主的想法结合。 思路就…

LSTM+Transformer混合模型架构文档

LSTMTransformer混合模型架构文档 模型概述 本项目实现了一个LSTMTransformer混合模型&#xff0c;用于超临界机组协调控制系统的数据驱动建模。该模型结合了LSTM的时序建模能力和Transformer的自注意力机制&#xff0c;能够有效捕捉时间序列数据中的长期依赖关系和变量间的复…

测量尺子:多功能测量工具,科技改变生活

测量尺子是一款专业的测距仪测量万能工具箱类型手机APP&#xff0c;旨在为用户提供最贴心的测量助手。它拥有和现实测量仪器一样的测量标准&#xff0c;更简单便捷且精准的测量方式&#xff0c;最新AR科技测量更是大大拓宽了可以被测量的高度和深度。无论是日常使用、学习还是工…

结课作业01. 用户空间 MPU6050 体感鼠标驱动程序

目录 一. qt界面实现 二. 虚拟设备模拟模拟鼠标实现体感鼠标 2.1 函数声明 2.2 虚拟鼠标实现 2.2.1 虚拟鼠标创建函数 2.2.2 鼠标移动函数 2.2.3 鼠标点击函数 2.3 mpu6050相关函数实现 2.3.1 i2c设备初始化 2.3.2 mpu6050寄存器写入 2.3.3 mpu6050寄存器读取 2.3.…

深入浅出 Python Testcontainers:用容器优雅地编写集成测试

在现代软件开发中&#xff0c;自动化测试已成为敏捷开发与持续集成中的关键环节。单元测试可以快速验证函数或类的行为是否符合预期&#xff0c;而集成测试则确保多个模块协同工作时依然正确。问题是&#xff1a;如何让集成测试可靠、可重复且易于维护&#xff1f; 这时&#…

JVM 的垃圾回收器

新生代回收器 通性 会触发StW&#xff0c;暂停所有应用线程复制算法 Serial 单线程回收适合单线程系统 ParNew 多线程回收优先保证响应速度&#xff0c;降低 STW&#xff08;STW 越大&#xff0c;执行垃圾回收的时间越长&#xff0c;回收的垃圾越多&#xff0c;减少垃圾回…

【笔记】排查并解决Error in LLM call after 3 attempts: (status code: 502)

#工作记录 一、问题描述 在部署运行部署对冲基金分析工具 ai-hedge-fund 时&#xff0c;不断出现以下报错&#xff0c;导致项目运行异常&#xff1a; Error in LLM call after 3 attempts: (status code: 502) Error in LLM call after 3 attempts: [WinError 10054] 远程主…

GO 语言进阶之 Template 模板使用

更多个人笔记见&#xff1a; github个人笔记仓库 gitee 个人笔记仓库 个人学习&#xff0c;学习过程中还会不断补充&#xff5e; &#xff08;后续会更新在github上&#xff09; 文章目录 Template 模板基本示例语法1. 基本输出语法2. 控制结构3. 空白字符控制4. Must函数 Temp…