深入解析 C++ 设计模式:原理、实现与应用

一、引言

        在 C++ 编程的广袤领域中,设计模式犹如闪耀的灯塔,为开发者指引着构建高效、可维护软件系统的方向。设计模式并非神秘莫测的代码魔法,实际上,我们在日常编程中或许早已与之打过交道。简单来说,设计模式常常借助多态这一强大特性,达成各式各样不同的操作方式。多态使得我们能够以统一的接口来应对不同类型的对象,为设计模式的实现筑牢了根基。接下来,让我们深入探索几种常见的设计模式。

二、策略模式

(一)模式概述

        策略模式包含一组策略类以及一个决策者类。其中,这组策略类是一系列继承自同一个基类的类。策略类承担着实现丰富多样不同策略的职责,而决策者类则负责挑选合适的策略并予以执行。

(二)代码实现

// 策略基类,定义了一个纯虚函数 move,任何继承自此类的子类都必须实现该函数
// 此基类为所有具体的移动策略提供了统一的接口
class MovementStrategy {
public:// 纯虚函数,用于执行具体的移动操作virtual void move() = 0;// 虚析构函数,确保在通过基类指针删除派生类对象时,能正确调用派生类的析构函数virtual ~MovementStrategy() {}
};// 步行策略类,继承自 MovementStrategy 基类
// 该类实现了步行这种具体的移动策略
class WalkStrategy : public MovementStrategy {
public:// 重写基类的 move 函数,实现步行操作的具体逻辑void move() override {std::cout << "Character is walking." << std::endl;}
};// 跑步策略类,继承自 MovementStrategy 基类
// 该类实现了跑步这种具体的移动策略
class RunStrategy : public MovementStrategy {
public:// 重写基类的 move 函数,实现跑步操作的具体逻辑void move() override {std::cout << "Character is running." << std::endl;}
};// 决策者类,负责选择和执行具体的移动策略
class Character {
private:// 指向当前使用的移动策略对象的指针MovementStrategy* currentStrategy;
public:// 构造函数,初始化角色的移动策略Character(MovementStrategy* strategy) : currentStrategy(strategy) {}// 设置角色的移动策略void setStrategy(MovementStrategy* strategy) {currentStrategy = strategy;}// 调用当前移动策略的 move 函数,执行移动操作void move() {currentStrategy->move();}// 析构函数,释放当前使用的移动策略对象所占用的内存~Character() {delete currentStrategy;}
};

(三)工作原理与实现逻辑

  1. 策略基类MovementStrategy作为策略基类,定义了纯虚函数move。这就为所有具体的移动策略(如步行、跑步等)制定了统一的接口规范。任何继承自该基类的子类都必须实现move函数,以提供具体的策略实现。同时,虚析构函数确保在通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,避免内存泄漏。
  2. 具体策略类WalkStrategyRunStrategy分别继承自MovementStrategy基类。它们通过重写move函数,实现了各自独特的移动策略。WalkStrategymove函数输出 “Character is walking.”,表示角色正在步行;RunStrategymove函数输出 “Character is running.”,表示角色正在跑步。
  3. 决策者类Character类作为决策者类,持有一个指向MovementStrategy对象的指针currentStrategy。在构造函数中,通过传入具体的策略对象来初始化currentStrategysetStrategy函数用于动态更改角色当前使用的移动策略。move函数则调用currentStrategy所指向对象的move函数,从而执行当前选定的移动策略。在析构函数中,释放currentStrategy所指向的对象,以确保内存的正确管理。

三、代理模式

(一)模式概述

        代理模式仅需一组类,但这组类包含两个具有不同功能的角色,即被代理者和代理者。

(二)代码实现

// 被代理类(远程数据库访问类)
// 该类负责实际从远程数据库获取数据
class RemoteDatabase {
public:// 获取远程数据库数据的函数std::string getData() {std::cout << "Accessing remote database..." << std::endl;return "Data from remote database";}
};// 代理类,用于代理对远程数据库的访问
// 引入缓存机制,减少对远程数据库的频繁访问
class DatabaseProxy {
private:// 指向被代理的远程数据库对象的指针RemoteDatabase* realDatabase;// 缓存从远程数据库获取的数据std::string cachedData;// 标记数据是否已缓存bool isCached;
public:// 构造函数,初始化被代理对象和缓存标记DatabaseProxy() : realDatabase(new RemoteDatabase), isCached(false) {}// 获取数据的函数,优先从缓存中获取,若缓存中没有则从远程数据库获取并缓存std::string getData() {if (isCached) {return cachedData;}cachedData = realDatabase->getData();isCached = true;return cachedData;}// 析构函数,释放被代理对象所占用的内存~DatabaseProxy() {delete realDatabase;}
};

(三)工作原理与实现逻辑

  1. 被代理类RemoteDatabase类代表被代理的远程数据库访问对象。其getData函数用于从远程数据库获取数据,并在获取过程中输出 “Accessing remote database...”,表示正在访问远程数据库,最后返回从数据库获取到的数据。
  2. 代理类DatabaseProxy类作为代理者,内部持有一个指向RemoteDatabase对象的指针realDatabase,以及用于缓存数据的cachedData和标记数据是否已缓存的isCached。在构造函数中,创建RemoteDatabase对象并初始化isCachedfalsegetData函数是代理模式的核心逻辑所在,首先检查isCached是否为true,若为true,则直接返回缓存中的数据cachedData,避免了再次访问远程数据库;若为false,则调用realDatabasegetData函数从远程数据库获取数据,将获取到的数据存入cachedData并将isCached设为true,最后返回数据。在析构函数中,释放realDatabase所指向的对象,确保内存的正确释放。

四、简单工厂模式

(一)模式概述

        简单工厂模式下存在两组类,一组是产品类,另一组是工厂类。产品类包含各种各样不同的产品,每个产品能执行不同的任务。工厂类则依据提供的不同 “原材料”,生产出相应的产品。

(二)代码实现

// 产品基类,定义了一个纯虚函数 getResult,任何继承自此类的子类都必须实现该函数
// 此基类为所有具体的计算产品提供了统一的接口
class Operation {
public:// 纯虚函数,用于执行具体的计算操作并返回结果virtual int getResult(int num1, int num2) = 0;// 虚析构函数,确保在通过基类指针删除派生类对象时,能正确调用派生类的析构函数virtual ~Operation() {}
};// 加法运算类,继承自 Operation 基类
// 该类实现了加法计算这种具体的产品功能
class OperationAdd : public Operation {
public:// 重写基类的 getResult 函数,实现加法计算的具体逻辑int getResult(int num1, int num2) override {return num1 + num2;}
};// 减法运算类,继承自 Operation 基类
// 该类实现了减法计算这种具体的产品功能
class OperationSub : public Operation {
public:// 重写基类的 getResult 函数,实现减法计算的具体逻辑int getResult(int num1, int num2) override {return num1 - num2;}
};// 工厂类,负责根据传入的操作符生产相应的计算产品对象
class OperationFactory {
public:// 静态工厂方法,根据传入的操作符创建对应的计算产品对象static Operation* createOperation(const std::string& operate) {if (operate == "+") {return new OperationAdd;} else if (operate == "-") {return new OperationSub;}return nullptr;}
};

(三)工作原理与实现逻辑

  1. 产品基类Operation作为产品基类,定义了纯虚函数getResult,用于执行具体的计算操作并返回结果。这为所有具体的计算产品(如加法、减法等)提供了统一的接口。任何继承自该基类的子类都必须实现getResult函数,以实现各自特定的计算功能。同时,虚析构函数确保在通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,防止内存泄漏。
  2. 具体产品类OperationAddOperationSub分别继承自Operation基类。它们通过重写getResult函数,实现了各自的计算逻辑。OperationAddgetResult函数将传入的两个数相加并返回结果,OperationSubgetResult函数则将两个数相减并返回结果。
  3. 工厂类OperationFactory类作为工厂类,提供了一个静态的createOperation方法。该方法接收一个表示操作符的字符串operate作为参数,根据operate的值判断需要创建的产品类型。如果operate为 “+”,则创建一个OperationAdd对象并返回;如果operate为 “-”,则创建一个OperationSub对象并返回;若传入的操作符不匹配任何已知的操作,则返回nullptr。这种方式使得产品的创建逻辑集中在工厂类中,方便管理和维护。

(四)优缺点

  • 优点:逻辑简单直白,易于理解和实现。对于简单的对象创建场景,使用简单工厂模式可以快速搭建起系统的对象创建框架。
  • 缺点:违反 “开闭原则”。当需要添加新的产品类(如乘法、除法运算类)时,必须修改工厂类的createOperation方法,添加新的条件判断逻辑。这不仅增加了修改现有代码的风险,还可能导致其他依赖该工厂类的代码出现问题,不利于系统的维护和拓展。

五、工厂方法模式

(一)模式概述

        工厂方法模式同样包含一组产品类和一组工厂类。该模式旨在解决简单工厂模式违反开闭原则的问题,它将每个产品都对应到一个独立的工厂类去生产,即每个工厂只能生产一种产品。

(二)代码实现

// 产品基类,定义了一个纯虚函数 getResult,任何继承自此类的子类都必须实现该函数
// 此基类为所有具体的计算产品提供了统一的接口
class Operation {
public:// 纯虚函数,用于执行具体的计算操作并返回结果virtual int getResult(int num1, int num2) = 0;// 虚析构函数,确保在通过基类指针删除派生类对象时,能正确调用派生类的析构函数virtual ~Operation() {}
};// 加法运算类,继承自 Operation 基类
// 该类实现了加法计算这种具体的产品功能
class OperationAdd : public Operation {
public:// 重写基类的 getResult 函数,实现加法计算的具体逻辑int getResult(int num1, int num2) override {return num1 + num2;}
};// 减法运算类,继承自 Operation 基类
// 该类实现了减法计算这种具体的产品功能
class OperationSub : public Operation {
public:// 重写基类的 getResult 函数,实现减法计算的具体逻辑int getResult(int num1, int num2) override {return num1 - num2;}
};// 加法工厂类,专门用于生产加法运算对象
class AddFactory {
public:// 静态工厂方法,创建并返回一个加法运算对象static Operation* createOperation() {return new OperationAdd;}
};// 减法工厂类,专门用于生产减法运算对象
class SubFactory {
public:// 静态工厂方法,创建并返回一个减法运算对象static Operation* createOperation() {return new OperationSub;}
};

(三)工作原理与实现逻辑

  1. 产品基类与具体产品类:这部分与简单工厂模式类似,Operation作为产品基类定义了统一的接口getResultOperationAddOperationSub等具体产品类继承自Operation基类,并实现了各自的计算逻辑。
  2. 工厂类:在工厂方法模式中,每个产品都有对应的工厂类。AddFactory专门用于创建OperationAdd对象,其createOperation方法返回一个新的OperationAdd实例;SubFactory专门用于创建OperationSub对象,其createOperation方法返回一个新的OperationSub实例。当需要添加新的产品类(如乘法运算类OperationMul)时,只需创建对应的工厂类(如MulFactory),并在其中实现创建OperationMul对象的逻辑,而无需修改现有的工厂类代码,从而遵循了开闭原则。

(四)优缺点

  • 缺点:前期代码量大,因为每一个产品都需要一个独立工厂去生产,这无疑增加了代码的编写量和维护成本。例如,若有多种不同类型的计算产品,就需要创建同样数量的工厂类。
  • 优点:遵循了开闭原则,易于维护拓展。当系统需要添加新的产品时,只需创建新的产品类和对应的工厂类,而不会影响到现有的代码。同时,独立工厂生产能够方便地进行定制化,比如可以在工厂类中添加特定的初始化逻辑或资源分配逻辑,以满足不同产品的特殊需求。

六、抽象工厂模式

(一)模式概述

        抽象工厂模式包含多组产品类和一组工厂类。工厂类能够生产多个产品,并且不同的工厂生产的产品品牌不同。

(二)代码实现

// 空调抽象类,定义了一个纯虚函数 showInfo,任何继承自此类的子类都必须实现该函数
// 此基类为所有具体品牌的空调产品提供了统一的接口
class AirConditioner {
public:// 纯虚函数,用于展示空调的信息virtual void showInfo() = 0;// 虚析构函数,确保在通过基类指针删除派生类对象时,能正确调用派生类的析构函数virtual ~AirConditioner() {}
};// 美的空调类,继承自 AirConditioner 基类
// 该类实现了美的品牌空调的具体信息展示功能
class MideaAirConditioner : public AirConditioner {
public:// 重写基类的 showInfo 函数,展示美的空调的信息void showInfo() override {std::cout << "This is Midea air conditioner." << std::endl;}
};// 格力空调类,继承自 AirConditioner 基类
// 该类实现了格力品牌空调的具体信息展示功能
class GreeAirConditioner : public AirConditioner {
public:// 重写基类的 showInfo 函数,展示格力空调的信息void showInfo() override {std::cout << "This is Gree air conditioner." << std::endl;}
};// 电冰箱抽象类,定义了一个纯虚函数 showInfo,任何继承自此类的子类都必须实现该函数
// 此基类为所有具体品牌的电冰箱产品提供了统一的接口
class Refrigerator {
public:// 纯虚函数,用于展示电冰箱的信息virtual void showInfo() = 0;// 虚析构函数,确保在通过基类指针删除派生类对象时,能正确调用派生类的析构函数virtual ~Refrigerator() {}
};// 美的电冰箱类,继承自 Refrigerator 基类
// 该类实现了美的品牌电冰箱的具体信息展示功能
class MideaRefrigerator : public Refrigerator {
public:// 重写基类的 showInfo 函数,展示美的电冰箱的信息void showInfo() override {std::cout << "This is Midea refrigerator." << std::endl;}
};// 格力电冰箱类,继承自 Refrigerator 基类
// 该类实现了格力品牌电冰箱的具体信息展示功能
class GreeRefrigerator : public Refrigerator {
public:// 重写基类的 showInfo 函数,展示格力电冰箱的信息void showInfo() override {std::cout << "This is Gree refrigerator." << std::endl;}

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

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

相关文章

Python刷题笔记

Python刷题笔记 1、输出格式化 第一种格式化的输出&#xff1a; name "jack" age 17 salary 20031.8752 print("你的名字是&#xff1a;%s,今年 %d 岁,工资 %7.2f" % (name,age,salary) ) --------------------------------------- 你的名字是&#…

【Kubernetes】Kubernetes 如何进行日志管理?Fluentd / Loki / ELK 适用于什么场景?

由于 Kubernetes 运行在容器化的环境中&#xff0c;应用程序和系统日志通常分布在多个容器和节点上&#xff0c;传统的日志管理方法&#xff08;例如直接访问每个节点的日志文件&#xff09;在 Kubernetes 中不适用。 因此&#xff0c;Kubernetes 引入了集中式日志管理方案&am…

Ansible(8)——循环与条件任务

目录 一、循环迭代任务&#xff1a; 1、简单循环&#xff1a; 2、循环字典列表&#xff1a; 3、Ansible 2.5 之前的循环关键字&#xff1a; 4、在循环中使用 register 变量&#xff1a; 二、条件任务&#xff1a; 1、使用条件句的常见场景&#xff1a; 2、条件任务语法…

adb|scrcpy的安装和配置方法|手机投屏电脑|手机声音投电脑|adb连接模拟器或手机

adb|scrcpy的安装和配置方法手机投屏电脑|手机声音投电脑|adb连接模拟器或手机或电视 引言 在数字设备交织的现代生活中&#xff0c;adb&#xff08;Android Debug Bridge&#xff09;与 scrcpy 宛如隐匿的强大工具&#xff0c;极大地拓展了我们操控手机、模拟器乃至智能电视等…

vue3项目集成electron

一、环境准备 1. 确保已安装 Node.js (建议版本 16.x 或更高) 2. 创建或进入现有 Vue 项目目录 cd your-vue-project 二、添加 Electron 支持 在项目根目录执行: vue add electron-builder 执行后会在 `src` 目录下生成 `background.js` 主进程文件。 三、主进程配置 (ba…

循环神经网络 - 参数学习之随时间反向传播算法

本文中&#xff0c;我们以同步的序列到序列模式为例来介绍循环神经网络的参数学习。 循环神经网络中存在一个递归调用的函数 &#x1d453;(⋅)&#xff0c;因此其计算参数梯度的方式和前馈神经网络不太相同。在循环神经网络中主要有两种计算梯度的方式&#xff1a;随时间反向…

体验OceanBase的 并行导入功能

在数据库的日常使用中&#xff0c;会经常遇到以下场景&#xff1a; ‌数据复制‌&#xff1a;将一个或多个表中的数据复制到目标表中&#xff0c;可能是复制全部数据&#xff0c;也可能仅复制部分数据。数据合并&#xff1a;将数据从一个表转移到另一个表&#xff0c;或者将多…

Kafka和RocketMQ相比有什么区别?那个更好用?

Kafka和RocketMQ相比有什么区别?那个更好用? Kafka 和 RocketMQ 都是广泛使用的消息队列系统&#xff0c;它们有很多相似之处&#xff0c;但也有一些关键的区别。具体选择哪个更好用&#xff0c;要根据你的应用场景和需求来决定。以下是它们之间的主要区别&#xff1a; 1. …

UniApp 实现兼容 H5 和小程序的拖拽排序组件

如何使用 UniApp 实现一个兼容 H5 和小程序的 九宫格拖拽排序组件&#xff0c;实现思路和关键步骤。 一、实现目标 支持拖动菜单项改变顺序拖拽过程实时预览移动位置拖拽松开后自动吸附回网格兼容 H5 和小程序平台 二、功能结构拆解以及完整代码 完整代码&#xff1a; <…

[raspberrypi 0w and respeaker 2mic]实时音频波形

0. 环境 ubuntu22主机&#xff0c; 192.168.8.162&#xff0c; raspberry 0w&#xff0c; 192.168.8.220 路由器 1. 树莓派 # rpi - send.py # 或者命令行&#xff1a;arecord -D plughw:1,0 -t wav -f cd -r 16000 -c 2 | nc 192.168.8.162 12345import socket imp…

公司内部建立apt源

有一篇建立pypi源的在这里需要的可以查看&#xff1a;公司内部建立pypi源-CSDN博客 背景&#xff0c;公司内部有很多工具仅供内部使用&#xff0c;如果用apt的方式就比较方便&#xff0c;只需要修改sources.list将源添加进去就可以了。我们接下来的操作就是为了实现这个需求。…

UE5中如何修复后处理动画蓝图带来的自然状态下的metablriger身体绑定形变(如耸肩)问题

【[metablriger] UE5中如何修复后处理动画蓝图带来的自然状态下的metablriger身体绑定形变(如耸肩)问题】 UE5中如何修复后处理动画蓝图带来的自然状态下的metablriger身体绑定形变(如耸肩)问题

AWS Bedrock生成视频详解:AI视频创作新时代已来临

💡 TL;DR: AWS Bedrock现已支持AI视频生成功能,让企业无需深厚AI专业知识即可创建高质量视频内容。本文详解Bedrock视频生成能力的工作原理、应用场景和实操指南,助你快速掌握这一革命性技术。 🎬 AWS Bedrock视频生成:改变内容创作的游戏规则 还记得几年前,制作一个专…

1.2 测试设计阶段:打造高质量的测试用例

测试设计阶段&#xff1a;打造高质量的测试用例 摘要 本文详细介绍了软件测试流程中的测试设计阶段&#xff0c;包括测试用例设计、测试数据准备、测试环境搭建和测试方案设计等内容。通过本文&#xff0c;读者可以系统性地了解测试设计的方法和技巧&#xff0c;掌握如何高效…

jQueryHTML与插件

1.jQuery 事件机制 1.1 注册事件 bind()、on()方法向被选元素添加一个或多个事件处理程序&#xff0c;以及当事件发生时运行的函数 $("p").on({"click": function () {alert("点击了")},"mouseenter": function () {…

MySQL 触发器与存储过程:数据库的自动化工厂

在数据世界的工业区&#xff0c;有一座运转高效的自动化工厂&#xff0c;那里的机器人日夜不停地处理数据…这就是 MySQL 的触发器与存储过程系统&#xff0c;它让数据库从"手工作坊"变成了"现代化工厂"… 什么是 MySQL 触发器与存储过程&#xff1f;&…

PostgreSQL-中文字段排序-修改字段的排序规则

最新版本更新 https://code.jiangjiesheng.cn/article/365?fromcsdn 推荐 《高并发 & 微服务 & 性能调优实战案例100讲 源码下载》 -- 修改字段的排序规则 ALTER TABLE "public"."your_table_name" ALTER COLUMN "name" TYPE varcha…

GitHub优秀项目:数据湖的管理系统LakeFS

lakeFS 是一个开源工具&#xff0c;它将用户的对象存储转换为类似Git的存储库。使用户可以像管理代码一样管理数据湖。借助 lakeFS&#xff0c;可以构建可重复、原子化和版本化的数据湖操作--从复杂的ETL作业到数据科学和分析。 Stars 数11090Forks 数3157 主要特点 强大的数据…

页面编辑器CodeMirror初始化不显示行号或文本内容

延迟刷新 本来想延迟100毫秒的&#xff0c;但是会出现样式向左偏移的情况&#xff0c;于是试了试500毫秒&#xff0c;发现就没有问题了&#xff0c;可能是样式什么是需要一个加载过程吧。 useEffect(() > {editorRef.current?.setValue(value || );setTimeout(() > {edi…

使用 Spring Boot 和 Uniapp 搭建 NFC 读取系统

目录 一、NFC 技术原理大揭秘1.1 NFC 简介1.2 NFC 工作原理1.3 NFC 应用场景 二、Spring Boot 开发环境搭建2.1 创建 Spring Boot 项目2.2 项目基本配置 三、Spring Boot 读取 NFC 数据3.1 NFC 设备连接与初始化3.2 数据读取逻辑实现3.3 数据处理与存储 四、Uniapp 前端界面开发…