C++设计模式(18)——模板方法模式
亦称: Template Method
意图
模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。

问题
假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式 (PDF、 DOC 或 CSV) 的文档, 程序则会试图从这些文件中抽取有意义的数据, 并以统一的格式将其返回给用户。
该程序的首个版本仅支持 DOC 文件。 在接下来的一个版本中, 程序能够支持 CSV 文件。 一个月后, 你 “教会” 了程序从 PDF 文件中抽取数据。

数据挖掘类中包含许多重复代码。
一段时间后, 你发现这三个类中包含许多相似代码。 尽管这些类处理不同数据格式的代码完全不同, 但数据处理和分析的代码却几乎完全一样。 如果能在保持算法结构完整的情况下去除重复代码, 这难道不是一件很棒的事情吗?
还有另一个与使用这些类的客户端代码相关的问题: 客户端代码中包含许多条件语句, 以根据不同的处理对象类型选择合适的处理过程。 如果所有处理数据的类都拥有相同的接口或基类, 那么你就可以去除客户端代码中的条件语句, 转而使用多态机制来在处理对象上调用函数。
解决方案
模板方法模式建议将算法分解为一系列步骤, 然后将这些步骤改写为方法, 最后在 “模板方法” 中依次调用这些方法。 步骤可以是 抽象的, 也可以有一些默认的实现。 为了能够使用算法, 客户端需要自行提供子类并实现所有的抽象步骤。 如有必要还需重写一些步骤 (但这一步中不包括模板方法自身)。
让我们考虑如何在数据挖掘应用中实现上述方案。 我们可为图中的三个解析算法创建一个基类, 该类将定义调用了一系列不同文档处理步骤的模板方法。

模板方法将算法分解为步骤, 并允许子类重写这些步骤, 而非重写实际的模板方法。
首先, 我们将所有步骤声明为 抽象类型, 强制要求子类自行实现这些方法。 在我们的例子中, 子类中已有所有必要的实现, 因此我们只需调整这些方法的签名, 使之与超类的方法匹配即可。
现在, 让我们看看如何去除重复代码。 对于不同的数据格式, 打开和关闭文件以及抽取和解析数据的代码都不同, 因此无需修改这些方法。 但分析原始数据和生成报告等其他步骤的实现方式非常相似, 因此可将其提取到基类中, 以让子类共享这些代码。
正如你所看到的那样, 我们有两种类型的步骤:
1、抽象步骤必须由各个子类来实现
2、可选步骤已有一些默认实现, 但仍可在需要时进行重写
还有另一种名为钩子的步骤。 钩子是内容为空的可选步骤。 即使不重写钩子, 模板方法也能工作。 钩子通常放置在算法重要步骤的前后, 为子类提供额外的算法扩展点。
真实世界类比

可对典型的建筑方案进行微调以更好地满足客户需求。
模板方法可用于建造大量房屋。 标准房屋建造方案中可提供几个扩展点, 允许潜在房屋业主调整成品房屋的部分细节。
每个建造步骤 (例如打地基、 建造框架、 建造墙壁和安装水电管线等) 都能进行微调, 这使得成品房屋会略有不同。
模板方法模式结构

1、抽象类 (AbstractClass) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。
2、具体类 (ConcreteClass) 可以重写所有步骤, 但不能重写模板方法自身。
伪代码
本例中的模板方法模式为一款简单策略游戏中人工智能的不同分支提供 “框架”。

一款简单游戏的 AI 类。
游戏中所有的种族都有几乎同类的单位和建筑。 因此你可以在不同的种族上复用相同的 AI 结构, 同时还需要具备重写一些细节的能力。 通过这种方式, 你可以重写半兽人的 AI 使其更富攻击性, 也可以让人类侧重防守, 还可以禁止怪物建造建筑。 在游戏中新增种族需要创建新的 AI 子类, 还需要重写 AI 基类中所声明的默认方法。
// 抽象类定义了一个模板方法,其中通常会包含某个由抽象原语操作调用组成的算
// 法框架。具体子类会实现这些操作,但是不会对模板方法做出修改。
class GameAI is// 模板方法定义了某个算法的框架。method turn() iscollectResources()buildStructures()buildUnits()attack()// 某些步骤可在基类中直接实现。method collectResources() isforeach (s in this.builtStructures) dos.collect()// 某些可定义为抽象类型。abstract method buildStructures()abstract method buildUnits()// 一个类可包含多个模板方法。method attack() isenemy = closestEnemy()if (enemy == null)sendScouts(map.center)elsesendWarriors(enemy.position)abstract method sendScouts(position)abstract method sendWarriors(position)// 具体类必须实现基类中的所有抽象操作,但是它们不能重写模板方法自身。
class OrcsAI extends GameAI ismethod buildStructures() isif (there are some resources) then// 建造农场,接着是谷仓,然后是要塞。method buildUnits() isif (there are plenty of resources) thenif (there are no scouts)// 建造苦工,将其加入侦查编组。else// 建造兽族步兵,将其加入战士编组。// ……method sendScouts(position) isif (scouts.length > 0) then// 将侦查编组送到指定位置。method sendWarriors(position) isif (warriors.length > 5) then// 将战斗编组送到指定位置。// 子类可以重写部分默认的操作。
class MonstersAI extends GameAI ismethod collectResources() is// 怪物不会采集资源。method buildStructures() is// 怪物不会建造建筑。method buildUnits() is// 怪物不会建造单位。
模板方法模式适合应用场景
当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。
模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。
当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。
在将算法转换为模板方法时, 你可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。
实现方式
1、分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同。
2、创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final最终修饰模板方法以防止子类对其进行重写。
3、虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法。
4、可考虑在算法的关键步骤之间添加钩子。
5、为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。
模板方法模式优缺点

与其他模式的关系
1、工厂方法模式是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。
2、模板方法基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略模式基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
C++ 模板方法模式讲解和代码示例
模版方法是一种行为设计模式, 它在基类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
使用示例: 模版方法模式在 C++ 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。
识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。
概念示例
本例说明了模板方法设计模式的结构并重点回答了下面的问题:
它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?
main.cc: 概念示例
/*** The Abstract Class defines a template method that contains a skeleton of some* algorithm, composed of calls to (usually) abstract primitive operations.** Concrete subclasses should implement these operations, but leave the template* method itself intact.*/
class AbstractClass {/*** The template method defines the skeleton of an algorithm.*/public:void TemplateMethod() const {this->BaseOperation1();this->RequiredOperations1();this->BaseOperation2();this->Hook1();this->RequiredOperation2();this->BaseOperation3();this->Hook2();}/*** These operations already have implementations.*/protected:void BaseOperation1() const {std::cout << "AbstractClass says: I am doing the bulk of the work\n";}void BaseOperation2() const {std::cout << "AbstractClass says: But I let subclasses override some operations\n";}void BaseOperation3() const {std::cout << "AbstractClass says: But I am doing the bulk of the work anyway\n";}/*** These operations have to be implemented in subclasses.*/virtual void RequiredOperations1() const = 0;virtual void RequiredOperation2() const = 0;/*** These are "hooks." Subclasses may override them, but it's not mandatory* since the hooks already have default (but empty) implementation. Hooks* provide additional extension points in some crucial places of the* algorithm.*/virtual void Hook1() const {}virtual void Hook2() const {}
};
/*** Concrete classes have to implement all abstract operations of the base class.* They can also override some operations with a default implementation.*/
class ConcreteClass1 : public AbstractClass {protected:void RequiredOperations1() const override {std::cout << "ConcreteClass1 says: Implemented Operation1\n";}void RequiredOperation2() const override {std::cout << "ConcreteClass1 says: Implemented Operation2\n";}
};
/*** Usually, concrete classes override only a fraction of base class' operations.*/
class ConcreteClass2 : public AbstractClass {protected:void RequiredOperations1() const override {std::cout << "ConcreteClass2 says: Implemented Operation1\n";}void RequiredOperation2() const override {std::cout << "ConcreteClass2 says: Implemented Operation2\n";}void Hook1() const override {std::cout << "ConcreteClass2 says: Overridden Hook1\n";}
};
/*** The client code calls the template method to execute the algorithm. Client* code does not have to know the concrete class of an object it works with, as* long as it works with objects through the interface of their base class.*/
void ClientCode(AbstractClass *class_) {// ...class_->TemplateMethod();// ...
}int main() {std::cout << "Same client code can work with different subclasses:\n";ConcreteClass1 *concreteClass1 = new ConcreteClass1;ClientCode(concreteClass1);std::cout << "\n";std::cout << "Same client code can work with different subclasses:\n";ConcreteClass2 *concreteClass2 = new ConcreteClass2;ClientCode(concreteClass2);delete concreteClass1;delete concreteClass2;return 0;
}
Output.txt: 执行结果
Same client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass1 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass1 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anywaySame client code can work with different subclasses:
AbstractClass says: I am doing the bulk of the work
ConcreteClass2 says: Implemented Operation1
AbstractClass says: But I let subclasses override some operations
ConcreteClass2 says: Overridden Hook1
ConcreteClass2 says: Implemented Operation2
AbstractClass says: But I am doing the bulk of the work anyway
来源:https://refactoringguru.cn/design-patterns/template-method
仅供学习,非商业用途,侵删
相关文章:
C++设计模式(18)——模板方法模式
亦称: Template Method 意图 模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 问题 假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式…...
SQLserver 索引碎片
Oracle 不需要整理碎片,原因? 1. rowid 默认的索引是B-树索引。索引建立在表中的一个或多个列或者是表的表达式上,将列值和行编号一起存储。行编号是唯一标记表中行的伪列。 行编号是物理表中的行数据的内部地址&am…...
【Storm】【二】安装
1 准备 1.1 准备linux服务器 本文搭建的是3节点的集群,需要3台linux服务器,我这里使用的是centos7版本的linux虚拟机,虚拟机网络配置如下: 主节点: master 192.168.92.90 从节点: slave1 192.168.92.…...
Android ConditionVariable
Android ConditionVariable 线程操作经常用到wait和notify,用起来稍显繁琐,而Android给我们封装好了一个ConditionVariable类,用于线程同步。提供了三个方法block()、open()、close()。 void block() //阻塞当前线程,直到条件为…...
Action Segmentation数据集介绍——Breakfast
文章目录简介细节Cooking actibitiesillustration of the actions论文讲解Breakfast(The Breakfast Action Dataset)简介 早餐动作数据集包括与早餐准备相关的10个动作,由18个不同厨房的52个不同的人执行。该数据集是最大的完全带注释的数据…...
横道图时间标尺在P6软件中的设置
卷首语 由于其直观简洁且易于管理的特性,使其成为展示项目活动顺序及时间安排的最常用的进度管理工具。 甘特图 甘特图(Gantt Chart),又称为横道图或棒条图,是最早的项目进度管理工具之一。由于其直观简洁且易于管理…...
空间复杂度(超详解+例题)
全文目录引言空间复杂度例题test1test2(冒泡排序)test3(求阶乘)test4(斐波那契数列)总结引言 在上一篇文章中,我们提到判断一个算法的好坏的标准是时间复杂度与空间复杂度。 时间复杂度的作用…...
Document-Level event Extraction via human-like reading process 论文解读
Document-Level event Extraction via human-like reading process 论文:2202.03092v1.pdf (arxiv.org) 代码:无 期刊/会议:ICASSP 2022 摘要 文档级事件抽取(DEE)特别困难,因为它提出了两个挑战:论元分散和多事件。第一个挑战…...
H5盲盒抽奖系统源码
盲盒抽奖系统4.0,带推广二维码防洪炮灰功能和教程。 支持微信无限回调登录 标价就是源码价格,vuetp5框架编写,H5网页,前后端分离 此源码为正规开发,正版产品已申请软著。 开源无加密无授权,可以二开使用…...
低代码平台和无代码平台哪个更适合开发企业管理系统?
编者按:本文分析了开发企业管理系统所需要的平台特性,并根据这些特点和低代码无代码的优劣比较,得出低代码平台更适合开发企业管理系统。关键词:私有化部署,可视化设计,源码交付,数据集成&#…...
75岁彪马再发NFT 复活美洲狮IP
在“运动品牌Web3”的潮流里,彪马(PUMA)绝对算是发烧友级别。2月22日,这家德国服装品牌的新NFT又来了,总量10000个Super PUMA NFT中,将有4000个以0.15 ETH(约为255美元)价格正式公售…...
大学生成人插画培训机构盘点
成人插画培训机构哪个好,成人学插画如何选培训班?给大家梳理了国内较好的插画培训机构排名,各有优势和特色,供大家参考! 一:国内成人插画培训机构排名 1、轻微课(五颗星) 主打课程有…...
【算法基础】一维差分 + 二维差分
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:【C/C】算法 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…...
游戏服务器框架 技能buff篇
游戏服务器框架 技能buff篇 1.状态 state 全局API 用于定义各种状态检查 bool IsDead(){ // 死亡buff if (buff->id 10001){ return true; } return false; } bool IsInvincible(){ if (buff->id 20001 || buff->id 20002){…...
网友说socket通信讲的不彻底,原来这才是Socket
关于对 Socket 的认识,大致分为下面几个主题,Socket 是什么,Socket 是如何创建的,Socket 是如何连接并收发数据的,Socket 套接字的删除等。 Socket 是什么以及创建过程 一个数据包经由应用程序产生,进入到…...
Nginx第二讲
目录 二、Nginx02 2.1 keepalived和heartbeat介绍 2.1.1 两者的介绍 2.1.2 keepalived简介 2.1.3 VRRP协议与工作原理 2.1.4 Keepalvied的工作原理 2.2 安装环境及keepalived 2.3 启动与验证keepalived 2.4 keepalived测试 2.4.1 环境准备 2.4.2 配置keepalived 2.…...
redis(win版)
1. 前言1.1 什么是RedisRedis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。基于内存存储,读写性能高适合存储热点数据&am…...
【Linux】编辑器——vim(最小集+指令集+自动化配置)
目录 1.vim最小集 1.1 vim的三种模式 1.2 vim的基本操作 2.vim指令集 2.1 命令模式指令集 移动光标 删除文字 复制 替换 撤销上一次操作 更改 跳至指定的行 2.2 底行模式指令集 列出行号 跳到文件中的某一行 查找字符 保存文件 多文件操作 3.如何配置vim 配…...
Centos7+Xshell+Jenkins堆装
windows系统崩坏,重装测试类工具,心情崩了 windows硬盘损坏前,运行应用具慢。。。。。。慢着慢着就走了 从前部署在本地的jenkins,python,gitblit等相关脚本都凉透了,所以这次把服务部署到Centos7上…...
Android system实战 — Android R(11) 进程保活白名单
Android system实战 — Android R 进程保活白名单0. 前言1. 具体实现1.1 准备工作1.2 源码实现1.2.1 源码1.2.2 diff文件0. 前言 最近在Android R上实现一些需求,进行记录一下,关于进程保活的基础知识可以参考Android system — 进程生命周期与ADJ&#…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
