C++软件设计模式之模板方法模式
模板方法模式是面向对象软件设计模式之一,其主要意图是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。
动机
在软件开发中,常常会遇到这样的情况:一个任务可以被分解为多个步骤来完成,其中有些步骤的实现方法是对所有子类通用的,而有些步骤则需要针对不同的子类有不同的实现方式。模板方法模式正是解决这一问题的有效手段。通过使用模板方法模式,可以将不变的部分代码抽离出来放在基类中,而将可变的部分留给子类去实现。这样不仅减少了代码的重复,而且还能够保证变与不变的部分之间的联系。
意图
模板方法模式的主要意图是定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。这样做可以让子类在不改变算法整体结构的前提下去重写算法的某些特定部分。
适用场合
- 不变的算法骨架:当某个抽象类有多个子类,但是它们有共同的算法步骤,只是某些步骤的具体实现不同,可以通过模版方法模式将这些不变的部分提取到抽象类中,子类只需要实现特定的步骤。
- 避免代码重复:多个类中若有一段代码完全相同或相似,可以考虑使用模版方法模式来抽取共享的部分,减少代码重复。
- 控制子类扩展:模版方法模式可以通过在模版方法中调用“钩子”方法,来控制子类在生命周期中的特定点上能够做什么,不能够做什么。钩子方法在抽象类中通常是空实现,子类可以覆盖这些方法来实现特定功能。
示例
模板方法模式的一个经典示例是咖啡店和茶馆的饮料制作过程。制作咖啡和茶的基本流程大致相同(准备热水、冲泡、倒入杯子、加调料),但具体的步骤(如冲泡的方式、加何种调料)则不同。通过定义一个模版方法来实现这个流程,基类可以控制整个流程的执行顺序,而具体的冲泡和加调料动作则由子类实现。
模板方法模式是一种非常有用的设计模式,在很多框架和库中都有应用。正确地使用模版方法模式可以使代码更加清晰、易于扩展和维护。
面是一个简单的C++示例,展示了如何使用模板方法模式来实现咖啡和茶的制作过程。在这个例子中,基类 Beverage
定义了一个模板方法 prepareRecipe()
,该方法包含了制作饮料的通用步骤,而具体的步骤(如冲泡和添加调料)则由子类实现。
代码示例
#include <iostream>
using namespace std;// 基类:Beverage
class Beverage {
public:// 模板方法,定义了制作饮料的算法骨架void prepareRecipe() {boilWater();brew();pourInCup();addCondiments();}// 子类不需要覆盖的方法,因为这些步骤对所有饮料都是相同的void boilWater() {cout << "Boiling water" << endl;}void pourInCup() {cout << "Pouring into cup" << endl;}// 子类必须覆盖的方法,因为这些步骤对不同饮料是不同的virtual void brew() = 0; // 纯虚函数,必须在子类中实现virtual void addCondiments() = 0; // 纯虚函数,必须在子类中实现
};// 子类:Coffee
class Coffee : public Beverage {
public:void brew() override {cout << "Dripping Coffee through filter" << endl;}void addCondiments() override {cout << "Adding Sugar and Milk" << endl;}
};// 子类:Tea
class Tea : public Beverage {
public:void brew() override {cout << "Steeping the tea" << endl;}void addCondiments() override {cout << "Adding Lemon" << endl;}
};int main() {cout << "Making Coffee..." << endl;Coffee coffee;coffee.prepareRecipe();cout << "\nMaking Tea..." << endl;Tea tea;tea.prepareRecipe();return 0;
}
运行结果
Making Coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and MilkMaking Tea...
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon
解释
-
基类
Beverage
:prepareRecipe()
是模板方法,定义了制作饮料的步骤:煮水、冲泡、倒入杯子、加调料。boilWater()
和pourInCup()
是所有饮料共有的步骤,因此在基类中实现。brew()
和addCondiments()
是纯虚函数,需要在子类中实现,因为这些步骤对不同的饮料有不同的实现。
-
子类
Coffee
和Tea
:Coffee
子类实现了brew()
和addCondiments()
,分别表示冲泡咖啡和添加糖和牛奶。Tea
子类实现了brew()
和addCondiments()
,分别表示泡茶和添加柠檬。
-
main
函数:- 创建
Coffee
和Tea
对象,并调用它们的prepareRecipe()
方法,输出了制作咖啡和茶的过程。
- 创建
适用场合
- 通用流程,具体步骤不同:当多个子类需要遵循相同的流程,但具体的步骤实现不同,可以使用模板方法模式。例如,不同的支付方式(信用卡、支付宝、微信)可以遵循相同的支付流程,但具体的支付接口和验证方式不同。
- 避免重复代码:在多个子类中存在相同的逻辑时,可以将这些逻辑提取到基类的模板方法中,避免代码重复。
模板方法模式通过将算法的结构固定,允许子类灵活地实现具体步骤,提供了一种优雅的方式来处理类似的任务。
模板方法模式经常与其他设计模式协同使用,以解决更复杂的设计问题。常见的协同模式包括策略模式、工厂方法模式和状态模式等。下面我们将重点介绍模板方法模式与策略模式的协同使用,并给出一个C++代码示例。
模板方法模式与策略模式的协同使用
动机
模板方法模式用于定义一个算法的骨架,而将某些步骤的实现延迟到子类中。策略模式用于定义一系列可互换的算法,并将这些算法封装在独立的类中。通过将策略模式与模板方法模式结合,可以在一个模板方法中使用不同的策略,从而提供更大的灵活性。
适用场景
- 算法的某些步骤需要根据不同的条件动态选择不同的实现:可以通过策略模式将这些步骤的实现封装在不同的策略类中,然后在模板方法中根据需要选择合适的策略。
- 需要在运行时动态改变算法的某些步骤:策略模式允许在运行时动态更换算法,而模板方法模式确保了算法的整体结构不变。
代码示例
假设我们有一个任务是处理数据,不同的处理策略可以根据不同的需求进行选择。我们使用模板方法模式来定义数据处理的骨架,使用策略模式来实现不同的处理策略。
1. 定义策略接口和具体策略
// 策略接口
class DataProcessingStrategy {
public:virtual void process() = 0;virtual ~DataProcessingStrategy() = default;
};// 具体策略1:压缩数据
class CompressStrategy : public DataProcessingStrategy {
public:void process() override {cout << "Compressing data" << endl;}
};// 具体策略2:加密数据
class EncryptStrategy : public DataProcessingStrategy {
public:void process() override {cout << "Encrypting data" << endl;}
};// 具体策略3:校验数据
class VerifyStrategy : public DataProcessingStrategy {
public:void process() override {cout << "Verifying data" << endl;}
};
2. 定义抽象类和模板方法
// 抽象类
class DataProcessor {
protected:DataProcessingStrategy* strategy;public:DataProcessor(DataProcessingStrategy* s) : strategy(s) {}virtual ~DataProcessor() {delete strategy;}// 模板方法void processData() {load();strategy->process();save();}virtual void load() {cout << "Loading data" << endl;}virtual void save() {cout << "Saving data" << endl;}
};
3. 定义具体的数据处理器
// 具体的数据处理器1:使用压缩策略
class CompressDataProcessor : public DataProcessor {
public:CompressDataProcessor() : DataProcessor(new CompressStrategy()) {}
};// 具体的数据处理器2:使用加密策略
class EncryptDataProcessor : public DataProcessor {
public:EncryptDataProcessor() : DataProcessor(new EncryptStrategy()) {}
};// 具体的数据处理器3:使用校验策略
class VerifyDataProcessor : public DataProcessor {
public:VerifyDataProcessor() : DataProcessor(new VerifyStrategy()) {}
};
4. 客户端代码
#include <iostream>
using namespace std;int main() {// 使用压缩策略处理数据cout << "Using Compress Strategy..." << endl;DataProcessor* processor1 = new CompressDataProcessor();processor1->processData();delete processor1;// 使用加密策略处理数据cout << "\nUsing Encrypt Strategy..." << endl;DataProcessor* processor2 = new EncryptDataProcessor();processor2->processData();delete processor2;// 使用校验策略处理数据cout << "\nUsing Verify Strategy..." << endl;DataProcessor* processor3 = new VerifyDataProcessor();processor3->processData();delete processor3;return 0;
}
运行结果
Using Compress Strategy...
Loading data
Compressing data
Saving dataUsing Encrypt Strategy...
Loading data
Encrypting data
Saving dataUsing Verify Strategy...
Loading data
Verifying data
Saving data
解释
-
策略接口
DataProcessingStrategy
:- 定义了一个纯虚函数
process()
,用于实现数据处理的具体策略。 - 具体策略
CompressStrategy
、EncryptStrategy
和VerifyStrategy
分别实现了数据压缩、加密和校验的策略。
- 定义了一个纯虚函数
-
抽象类
DataProcessor
:- 包含一个策略对象
strategy
,并通过构造函数传递具体的策略对象。 - 定义了模板方法
processData()
,该方法调用了load()
、strategy->process()
和save()
,确保数据处理的流程一致。 load()
和save()
是通用的数据加载和保存步骤,可以在子类中根据需要进行扩展。
- 包含一个策略对象
-
具体数据处理器类:
CompressDataProcessor
、EncryptDataProcessor
和VerifyDataProcessor
分别使用不同的策略对象初始化DataProcessor
。
-
客户端代码:
- 创建不同的数据处理器对象,并调用
processData()
方法来处理数据,输出了不同的处理策略的结果。
- 创建不同的数据处理器对象,并调用
通过这种方式,模板方法模式和策略模式的结合提供了更大的灵活性,允许在运行时动态选择不同的数据处理策略,同时保持数据处理流程的一致性。
相关文章:

C++软件设计模式之模板方法模式
模板方法模式是面向对象软件设计模式之一,其主要意图是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。 动机 在软件开发中,常常会遇到这样的情…...
神经网络的初始化方式都有哪些?
一、概念 神经网络的初始化是深度学习中的一个关键步骤,它指的是在训练开始前为神经网络的权重和偏置设置初始值。合适的初始化方法可以加速模型的收敛,提高训练效果,甚至影响模型的最终性能。当然,目前我们使用Torch、TensorFlow…...

const成员函数
在c中经常看到这样的声明: class A{ ... int fun1() const; //const成员函数 int fun2() const; //const成员函数private: int a; //属于状态 static int b; //不属于状态,属于类 } 这个const关键字声明了这个函数是const成员函数,con…...

物理知识1——电流
说起电流,应该从电荷说起,而说起电荷,应该从原子说起。 1 原子及其结构 常见的物质是由分子构成的,而分子又是由原子构成的,有的分子是由多个原子构成,有的分子只由一个原子构成。而原子的构成如图1所示。…...

车载通信架构 --- 智能汽车通信前沿技术
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 所谓鸡汤,要么蛊惑你认命,要么怂恿你拼命,但都是回避问题的根源,以现象替代逻辑,以情绪代替思考,把消极接受现实的懦弱,伪装成乐观面对不幸的…...

Flutter中添加全局防护水印的实现
随着版权意识的加强,越来越多的应用开始在应用内部增加各种各样的水印信息,防止核心信息泄露,便于朔源。 效果如下: 在Flutter中增加全局水印的方式,目前有两种实现。 方案一,在native层添加一个遮罩层&a…...

BGP(Border Gateway Protocol)路由收集器
全球 BGP(边界网关协议)路由收集器的分布情况以及相关数据。以下是主要的信息解读: 地图标记: 每个绿色点代表一个路由收集器的位置。路由收集器分布在全球不同的地区,覆盖了五大区域: ARIN(美…...

【DAGMM】直接跑tip
1.from sklearn.externals import joblib 版本高 joblib没有 直接pip install joblib,然后 import joblib 2.AttributeError: module ‘tensorflow’ has no attribute ‘set_random_seed’ # tf.set_random_seed(args.seed)#tf<2.0 tf.random.set_seed(args.s…...

vscode中调用deepseek实现AI辅助编程
来自 Python大数据分析 费弗里 1 简介 大家好我是费老师,最近国产大模型Deepseek v3新版本凭借其优秀的模型推理能力,讨论度非常之高🔥,且其官网提供的相关大模型API接口服务价格一直走的“价格屠夫”路线,性价比很高…...

AI大模型语音识别转文字
提取音频 本项目作用在于将常见的会议录音文件、各种语种音频文件进行转录成相应的文字,也可从特定视频中提取对应音频进行转录成文字保存在本地。最原始的从所给网址下载对应视频和音频进行处理。下载ffmpeg(https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-…...

可由 (5V) 单片机直接驱动的模块
可由 (5V) 单片机 直接驱动的模块 1. 传感器类 元器件描述温度传感器DS18B20(数字温度传感器)光强传感器光敏电阻(通过 ADC 读取)红外传感器红外接收模块(如 VS1838)超声波传感器HC…...

vue使用树形结构展示文件和文件夹
1. 树形结构显示 显示文件夹和文件:使用 el-tree 组件展示树形结构,文件夹和文件的图标通过 el-icon 进行动态显示。文件夹使用 Folder 图标,文件使用 Files 图标。节点点击:点击树形节点后,会将选中的节点保存到 sel…...

PHP框架+gatewayworker实现在线1对1聊天--聊天界面布局+创建websocket连接(5)
文章目录 聊天界面布局html代码 创建websocket连接为什么要绑定? 聊天界面布局 在View/Index目录下创建index.html html代码 <div id"chat"><div id"nbar"><div class"pull-left">与牛德胜正在聊天...</div…...

LinuxUbuntu打开VSCode白屏解决方案
解决方法是 以root权限打开VSCode sudo /usr/share/code/code --no-sandbox --unity-launch...

在 ESP 上运行 AWTK
AWTK 基于 esp 的移植。 测试硬件平台为 ESP32-S3-Touch-LCD-4.3,其它平台请根据实际平台自行调整。 安装下载工具 建议下载离线版本 ESP IDF v5.3.2 下载代码 git clone https://github.com/zlgopen/awtk-esp.git cd awtk-esp git clone https://github.com/zlg…...

硬件工程师面试题 21-30
把常见的硬件面试题进行总结,方便及时巩固复习。其中包括网络上的资源、大佬们的大厂面试题,其中可能会题目类似,加强印象即可。 更多硬件面试题:硬件工程师面试题 1-10硬件工程师面试题 11-20 21、单片机最小系统需要什么&#x…...

开源架构的容器化部署优化版
上三篇文章推荐: 开源架构的微服务架构实践优化版(New) 开源架构中的数据库选择优化版(New) 开源架构学习指南:文档与资源的智慧锦囊(New) 我管理的社区推荐:【青云交社区…...

Qt使用CMake编译项目时报错:#undefined reference to `vtable for MainView‘
博主将.h文件和.cpp文件放到了不同的文件目录下面,如下图所示: 于是构建项目的时候就报错了#undefined reference to vtable for MainView,这个是由于src/view目录下的CMake无法自动moc头文件导致的,需要手动moc include/view目录…...

python学习笔记—12—
1. 布尔类型 (1) 定义 (2) 比较运算符 (3) 代码演示 1. 手动定义 bool_1 True bool_2 False print(f"bool_1的内容是:{bool_1}, 类型是:{type(bool_1)}") print(f"bool_2的内容是:{bool_2}, 类型是:{type(bool…...

==和===的区别,被坑的一天
在 JavaScript 中, 和 都用于比较两个值,但它们有一个重要的区别: 1. (宽松相等运算符) 进行比较时,会 自动类型转换(也叫做强制类型转换),即如果比较的两个值的类型不同,JavaScr…...

基于 GPUTasker 的 GPU 使用情况钉钉推送机器人实现
引言 https://github.com/cnstark/gputasker 随着 AI 模型的广泛应用,GPU 成为团队中最重要的资源之一。然而,如何实时监控 GPU 的使用情况并及时通知团队是一个值得关注的问题。为了更好地管理显卡资源,本文基于 GPUTasker,实现了…...

Python自学 - 函数初步(内置函数、模块函数、自定义函数)
1 Python自学 - 函数初步(内置函数、模块函数、自定义函数) 1.1 内置函数 几乎所有的编程都会提供一些内置函数,以便完成一些最基本的任务,Python提供了丰富的内置函数,熟悉内置函数可以给工作带来极大便利。 Python官方的内置函数介绍网…...

【生活】冬天如何选口罩(医用口罩,N95, KN95还是KP95?带不带呼吸阀门?带不带活性炭?)
💡总结一下就是: 日常防护的话,医用口罩就可以啦。要是想长时间佩戴N95(KN95)口罩的话也可以. 在高风险环境(像医院、疫情防控期间),一定要选不带呼吸阀门的N95口罩KN95)…...

HTML5新特性|01 音频视频
音频 1、Audio (音频) HTML5提供了播放音频文件的标准 2、control(控制器) control 属性供添加播放、暂停和音量控件 3、标签: <audio> 定义声音 <source> 规定多媒体资源,可以是多个<!DOCTYPE html> <html lang"en"> <head><…...

迅为RK3568开发板编译Android12源码包-设置屏幕配置
在源码编译之前首先要确定自己想要使用的屏幕并修改源码,在编译镜像,烧写镜像。如下图所示: 第一步:确定要使用的屏幕种类,屏幕种类选择如下所示: iTOP-3568 开发板支持以下种类屏幕: 迅为 LV…...

力扣hot100——图论
200. 岛屿数量 class Solution { public:int numIslands(vector<vector<char>>& grid) {int ans 0;vector<int> dx { 0, 1, 0, -1 };vector<int> dy { 1, 0, -1, 0 };int n grid.size(), m grid[0].size();vector<vector<int>> …...

Docker- Unable to find image “hello-world“locally
Docker- Unable to find image “hello-world“locally 文章目录 Docker- Unable to find image “hello-world“locally问题描述一. 切换镜像1. 编辑镜像源2. 切换镜像内容 二、 检查设置1、 重启dockers2、 检查配置是否生效3. Docker镜像源检查4. Dokcer执行测试 三、自定义…...

spring-boot启动源码分析(二)之SpringApplicationRunListener
在上一篇《spring-boot启动源码分析(一)之SpringApplication实例构造》后,继续看了一个月的Spring boot启动源码,初步把流程看完了,接下来会不断输出总结,以巩固这段时间的学习。同时也希望能帮到同样感兴趣…...

ELK入门教程(超详细)
什么是ELK? ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称(后来出现的filebeat属于beats家族中的一员,可以用来替代logstash的数据收集功能,比较轻量级),也被称为Elastic Stack。 Filebeat Filebeat是用于转…...

人工智能知识分享第六天-机器学习_逻辑回归(Logistic Regression)
简介 在机器学习中,分类问题是一种常见的任务,目标是根据输入特征将数据点分配到不同的类别中。为了实现分类,我们需要训练一个分类器,该分类器能够根据输入数据的特征进行预测。 逻辑回归(Logistic Regression&…...