C++设计模式_03_模板方法Template Method
文章目录
- 1. 设计模式分类
- 1.1 GOF-23 模式分类
- 1.2 从封装变化角度对模式分类
- 2. 重构(使用模式的方法)
- 2.1 重构获得模式 Refactoring to Patterns
- 2.2 重构关键技法
- 3. “组件协作”模式
- 4. Template Method 模式
- 4.1 动机( Motivation)/应用场景
- 4.1.1 结构化软件设计流程
- 4.1.2 面向对象软件设计流程
- 4.1.3 对比两种写法:
- 4.2 早绑定和晚绑定
- 4.3 模式定义
- 4.4 结构( Structure)
- 4.5 要点总结
上篇介绍了面向对象设计的原则和目标之后,本篇将会介绍非常经典,并且具有示范效应的模式-模板方法Template Method。Template Method模式是一种
非常基础性
的设计模式,在面向对象系统中有着大量的应用。它用
最简洁的机制(虚函数的多态性)
为很多应用程序框架提供了灵活的
扩展点(继承+多态)
,是代码复用方面的基本实现结构。(
只要写过面向对象的应用程序,一定用过Template Method,只是可能没有写过核心流程)
1. 设计模式分类
1.1 GOF-23 模式分类
首先看一下,在《设计模式:可复用面向对象软件的基础》中对23种设计模式整体有如下分类方法:
- 从目的来看:
- 创建型( Creational) 模式: 将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象
创建时
具体类型实现引来的冲击。 - 结构型( Structural) 模式: 通过类继承或者对象组合获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击。
- 行为型( Behavioral) 模式 : 通过类继承或者对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击。
- 创建型( Creational) 模式: 将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象
- 从范围(实现手段)来看
- 类模式处理类与子类的静态关系:更偏重于继承方案
- 对象模式处理对象间的动态关系:更偏重于组合方案
1.2 从封装变化角度对模式分类
在实践中总结的一种分类方式如下:
- 组件协作:解决协作问题
- Template Method
- Observer / Event
- Strategy
- 单一职责:解决类与类之间责任划分的问题
- Decorator
- Bridge
- 对象创建:解决对象创建过程中的依赖关系
- Factory Method
- Abstract Factory
- Prototype
- Builder
- 对象性能:
- Singleton
- Flyweight
- 接口隔离:
- Façade
- Proxy
- Mediator
- Adapter
- 状态变化:
- Memento
- State
- 数据结构:
- Composite
- Iterator
- Chain of Resposibility
- 行为变化:
- Command
- Visitor
- 领域问题:
- Interpreter
2. 重构(使用模式的方法)
2.1 重构获得模式 Refactoring to Patterns
学习设计模式中非常重要的方法
-
面向对象设计模式是
“好的面向对象设计”
,所谓“好的面向对象设计”指是那些可以满足“应对变化,提高复用”
的设计 。 -
现代软件设计的特征是“需求的频繁变化”。设计模式的要点是“
寻找变化点,然后在变化点处应用设计模式
,从而来更好地应对需求的变化” 。“什么时候、什么地点应用设计模式”比“理解设计模式结构本身”更为重要。 -
设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用。没有一步到位的设计模式。敏捷软件开发实践提倡的“Refactoring to Patterns” 是目前普遍公认的最好的使用设计模式的方法。
对本条的理解:没有使用模式的情况下,代码的结构关系是怎样的,存在什么样的问题,违背了怎样的设计原则,通过迭代重构的方式去修正他,通过修正得到了一种良好的解决方案,得到一种模式。推荐在工作中也是采用这种方式来得到一种模式,除非你已经在该领域有丰富的经验,从而对模式的使用很有把握。 -
推荐图书
这两本书代码和思想是与后期博文介绍的思想都是很类似的。
2.2 重构关键技法
以下的五种重构技巧,目前的理解可能还不够到位,但是原则很重要,技法也是很重要。
-
静态 -> 动态
-
早绑定 -> 晚绑定
-
继承 -> 组合
-
编译时依赖 -> 运行时依赖
-
紧耦合 -> 松耦合
其实上面五种技巧讲的是一件事情,也可以看做是从不同角度看待同一个问题。
3. “组件协作”模式
-
现代软件专业分工之后的第一个结果是
“框架与应用程序的划分”
,“组件协作”模式通过晚期绑定
,来实现框架与应用程序之间的松耦合
,是二者之间协作时常用的模式。 -
典型模式
- Template Method 模板方法
- Strategy 策略模式
- Observer / Event 事件模式
当我们介绍这三种模式是“组件协作”模式时,并不是说其他模式与“组件协作”模式没有关系,其实也有关系,只是上述三种模式体现的最为强烈,特征表现特别明显。
4. Template Method 模式
4.1 动机( Motivation)/应用场景
-
在软件构建过程中,对于某一项任务,它常常有
稳定
的整体操作结构,但各个子步骤却有很多改变
的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。 -
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
4.1.1 结构化软件设计流程
有以下代码,程序库开发人员开发的class Library,其中包含了完成某个任务的几个步骤,假设为step1,step3,step5。
(1)template1_lib.cpp:
//程序库开发人员
class Library{public:void Step1(){//...}void Step3(){//...}void Step5(){//...}
};
作为应用程序开发人员,为了实现功能,class Application中也做了step2,step4这2个步骤,并且开发了main函数。
完成步骤的建立之后,在main函数中,以某种具体流程串起来。以下代码中线执行流程为:先执行lib.Step1
,再根据app.Step2
的返回值来执行lib.Step3
,再重复执行app.Step4
四次,最后执行lib.Step5
(2)template1_app.cpp:
//应用程序开发人员
class Application{
public:bool Step2(){//...}void Step4(){//...}
};int main()
{Library lib();Application app();lib.Step1();if (app.Step2()){lib.Step3();}for (int i = 0; i < 4; i++){app.Step4();}lib.Step5();}
博文中展示的代码是没有遵循C++的编码规范的。
4.1.2 面向对象软件设计流程
还有第二种做法
程序库开发人员开发的class Library,除了包含step1,step3,step5,同时也将step2,step4写下,但是不做实现。
大家在以前的开发中经常会碰到代码样例,早期做windows程序开发时,微软会推荐先做windows典型应用程序的流程,会提供类似的样例代码,说明需要做什么步骤,哪些可以直接调用,就像step1,step3,step5,哪些需要自己去写,例如step2,step4。
其实框架人员已经开发好了整体流程,常常是不需要更改,也就是稳定的,所以框架开发人员或者说程序库开发人员,完全可以将流程写下来,这里的流程是和上面代码表达的流程是一样的,只不过是由框架开发人员去写。
(1)template2_lib.cpp:
//程序库开发人员
class Library{
public://稳定 template methodvoid Run(){Step1();if (Step2()) { //支持变化 ==> 虚函数的多态调用Step3(); }for (int i = 0; i < 4; i++){Step4(); //支持变化 ==> 虚函数的多态调用}Step5();}virtual ~Library(){ }protected:void Step1() { //稳定//.....}void Step3() {//稳定//.....}void Step5() { //稳定//.....}//框架开发人员无法决定怎么去写,留给子类去重写virtual bool Step2() = 0;//变化virtual void Step4() =0; //变化
};
(2)template2_app.cpp:
子类作为应用程序开发人员,重写Step2,Step4。
在Library* pLib=new Application()
中pLib
是多态指针,其声明类型为Library,实际类型为Application,当他调用虚函数的时候,就会按照虚函数动态绑定的规则去调用。
lib->Run()
是非虚函数,但是其里面Step2,Step4是虚函数,因此其会按照虚函数的调用规则去找子类Application的实现。
//应用程序开发人员
class Application : public Library {
protected:virtual bool Step2(){//... 子类重写实现}virtual void Step4() {//... 子类重写实现}
};int main(){Library* pLib=new Application();lib->Run();delete pLib;}
}
细节:
virtual ~Library(){ }
:在C++中写一个基类,有一条原则就是将基类中的析构函数写成虚的,这样就可以在delete pLib
调用到子类的析构函数
4.1.3 对比两种写法:
方法一 是一种结构化软件设计流程,其结构图如下:
方法二 是面向对象软件设计流程,其结构图如下:
在方法二中将程序主流程写到了Library中,应用程序就相对写的少了。而且可以看到方法一中是蓝色框调用红色框,而方法二中是红色框调用蓝色框。
4.2 早绑定和晚绑定
对上述方法进行梳理可以看到第一种方法是一种早绑定的方法,因为Library天然是写的早的,Application写的晚,利用晚的东西调用早的东西就是早绑定,这是编程语言默认的做法。
但是在面向对象软件语言以后,Library还是写的早的,Application还是写的晚,而使用Library调用Application,这样就是晚绑定。
4.3 模式定义
GoF中对模板方法模式的定义如下:
定义一个操作中的算法的骨架(对应第二种方法的run函数) (稳定
),而将一些步骤延迟(延迟一般代表定义一个虚函数,让子类去实现虚函数,也就是支持子类来变化)(变化
)到子类中。 Template Method使得子类可以不改变(复用
)一个算法的结构,即可重定义(override 重写)该算法的某些特定步骤。 —《设计模式》GoF
可以参考方法二中的代码进行映射理解的。以下代码深刻揭示了绝大多数设计模式的最核心的结构特点就是稳定中有变化,run()是稳定的,Step2()、Step4()是变化的,在C++语言层面体现出来的就是,稳定的代码需要写成非虚函数,要支持变化的要写成虚函数。
//稳定 template methodvoid Run(){Step1();if (Step2()) { //支持变化 ==> 虚函数的多态调用Step3(); }for (int i = 0; i < 4; i++){Step4(); //支持变化 ==> 虚函数的多态调用}Step5();}
那么这种模式有什么缺点?
前面假定Run()是稳定的,但是假如Run()不稳定了,也就不适合使用Template Method,因此该设计模式使用的前提就是Run()是稳定的。
当软件体系结构中所有都不稳定的时候,任何一种模式都不可使用,这是因为设计模式假设条件是必须有一个稳定点。
反过来,当Step2()、Step4()都是稳定的时候,设计模式也就没有使用的意义。
设计模式最大的作用就是变化和稳定之间寻找隔离点,然后来分离他们,从而来管理变化
,按照compact的讲法(不懂什么一次),就是将变化像小兔子一样关进笼子,让其在笼子里跳,而不至于跳出来将整个房间污染。所以正常的软件结构,一定是既有变化又有稳定点的。
在模式应用的时候,核心就是分辨出来软件体系结构中,哪些是稳定的,哪些是变化的。
有了这个意识之后,再看下图,程序主流程Run()为什么放在红框中,这是因为我们假定他是想对稳定的,更具有复用价值。
绝大对数软件框架中都有Template Method模式,相对于第一种方法,使用Template Method模式,应用程序的核心流程是塞到父类里,所以应用程序开发人员是看不到变化过程,只需要写几个步骤就可以了。
所以在面向对象的Template Method模式下,如果你是application的开发人员,经常会面对一种“只见树木,不见森林”的困惑,因为你只是在写子步骤,而没有去写核心流程。
4.4 结构( Structure)
上图是《设计模式》GoF中定义的Template Method的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
看任何设计模式的时候,包括其类图,都画一画哪些是稳定的,哪些是变化的,当你形成了这样一种习惯之后,你对模式的理解会更上一层楼,而不是只看其代码关系。
4.5 要点总结
-
Template Method模式是一种
非常基础性
的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)
为很多应用程序框架提供了灵活的扩展点(继承+多态)
,是代码复用方面的基本实现结构。(只要写过面向对象的应用程序,一定用过Template Method,只是可能没有写过核心流程) -
除了可以灵活应对子步骤的变化外,
“不要调用我,让我来调用你”
的反向控制结构是Template Method的典型应用。- 在Template Method设计之前,软件体系架构的主流是应用程序开发人员来调用Library开发人员写的代码,当面向对象的设计模式成为主流之后,调用关系反转,让早写的来调用晚写的,而依靠的机制就是虚函数的机制,也就是虚函数的晚绑定机制
- 虚函数是面向对象里面最核心的晚绑定机制,但是任何一个编程语言的晚绑定机制不只有虚函数,像C++中还有函数指针,但是函数指针在某些场合不具有虚函数的抽象性
-
在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法(前后有流程环境才有意义)。
相关文章:

C++设计模式_03_模板方法Template Method
文章目录 1. 设计模式分类1.1 GOF-23 模式分类1.2 从封装变化角度对模式分类 2. 重构(使用模式的方法)2.1 重构获得模式 Refactoring to Patterns2.2 重构关键技法 3. “组件协作”模式4. Template Method 模式4.1 动机( Motivationÿ…...

【LeetCode-中等题】79. 单词搜索
文章目录 题目方法一:递归 回溯 题目 方法一:递归 回溯 需要一个标记数组 来标志格子字符是否被使用过了先找到word 的第一个字符在表格中的位置,再开始递归递归的结束条件是如果word递归到了最后一个字符了,说明能在矩阵中找到单…...

揭秘iPhone 15 Pro Max:苹果如何战胜三星
三星Galaxy S23 Ultra在我们的最佳拍照手机排行榜上名列前茅有几个原因,但iPhone 15 Pro Max正在努力夺回榜首——假设它有一个特定的功能。别误会我的意思,苹果一直在追赶三星,因为它的iPhone 14 Pro和14 Pro Max都表现强劲。尽管如此&#…...

分布式秒杀方案--java
前提:先把商品详情和秒杀商品缓存redis中,减少对数据库的访问(可使用定时任务) 秒杀商品无非就是那几步(前面还可能会有一些判断,如用户是否登录,一人一单,秒杀时间验证等࿰…...

高频golang面试题:简单聊聊内存逃逸?
文章目录 问题怎么答举例 问题 知道golang的内存逃逸吗?什么情况下会发生内存逃逸? 怎么答 golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。…...
【2023年数学建模国赛C题解题思路】
第一问 要求分析分析蔬菜各品类及单品销售量的分布规律及相互关系。该问题可以拆分成三个角度进行剖析。 1)各种类蔬菜的销售量分布、蔬菜种类与销售量之间的关系;2)各种类蔬菜的销售量的月份分布、各种类蔬菜销售量与月份之间的相关关系&a…...

Jenkins+Allure+Pytest的持续集成
一、配置 allure 环境变量 1、下载 allure是一个命令行工具,可以去 github 下载最新版:https://github.com/allure-framework/allure2/releases 2、解压到本地 3、配置环境变量 复制路径如:F:\allure-2.13.7\bin 环境变量、Path、添加 F:\a…...

yo!这里是进程控制
目录 前言 进程创建 fork()函数 写时拷贝 进程终止 退出场景 退出方法 进程等待 等待原因 等待方法 1.wait函数 2.waitpid函数 等待结果(status介绍) 进程替换 替换原理 替换函数 进程替换例子 shell简易实现 后记 前言 学习完操作…...

多线程快速入门
线程与进程区别 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里…...
Redis 7 第七讲 哨兵模式(sentinal)架构篇
哨兵模式 哨兵巡查监控后台master主机是否故障,如果出现故障根据投票时自动将某一个从库转换成新的主库,继续对外服务。 作用 1. 监控redis运行状态,包括master和slave 2. 当master down机,能自动将salve切换成新的master 应用场景 主从监控监控主从redis库运行的状态…...

laravel框架系列(一),Dcat Admin 安装
介绍 Laravel 是一个流行的 PHP 开发框架,它提供了一套简洁、优雅的语法和丰富的功能,用于快速构建高质量的 Web 应用程序。 以下是 Laravel 的一些主要特点和功能: MVC 架构:Laravel 使用经典的模型-视图-控制器(MV…...

Linux:工具(vim,gcc/g++,make/Makefile,yum,git,gdb)
目录 ---工具功能 1. vim 1.1 vim的模式 1.2 vim常见指令 2. gcc/g 2.1 预备知识 2.2 gcc的使用 3.make,Makefile make.Makefile的使用 4.yum --yum三板斧 5.git --git三板斧 --Linux下提交代码到远程仓库 6.gdb 6.1 gdb的常用指令 学习目标: 1.知道…...

小节1:Python字符串打印
1、字符串拼接 用可以将两个字符串拼接成一个字符串 print("你好 " "这是一串代码") 输出: 2、单双引号转义 当打印的字符串中带有引号或双引号时,使用\或\"表示 print("He said \"Let\s go!\"") 输…...

2023国赛C题解题思路代码及图表:蔬菜类商品的自动定价与补货决策
2023国赛C题:蔬菜类商品的自动定价与补货决策 C题表面上看上去似乎很简单,实际上23题非常的难,编程难度非常的大,第二题它是一个典型的动态规划加仿真题目,我们首先要计算出销量与销售价格,批发价格之间的…...

数据可视化工具中的显眼包:奥威BI自带方案上阵
根据经验来看,BI数据可视化分析项目是由BI数据可视化工具和数据分析方案两大部分共同组成,且大多数时候方案都需从零开始,反复调整,会耗费大量时间精力成本。而奥威BI数据可视化工具别具匠心,将17年经验凝聚成标准化、…...

LeetCode算法心得——生成特殊数字的最少操作(贪心找规律)
大家好,我是晴天学长,这是一个简单贪心思维技巧题,主要考察的还是临场发挥的能力。需要的小伙伴可以关注支持一下哦!后续会继续更新的。 2) .算法思路 0 00 50 25 75 末尾是这两个的才能被45整除 思路:分别找&#x…...

【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献
【2023高教社杯】B题 多波束测线问题 问题分析、数学模型及参考文献 1 题目 1.1 问题背景 多波束测深系统是利用声波在水中的传播特性来测量水体深度的技术,是在单波束测深的基础上发展起来的,该系统在与航迹垂直的平面内一次能发射出数十个乃至上百个…...

如何处理异步编程中的回调地狱问题?
聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 解决回调地狱问题的方法⭐使用 Promise⭐使用 async/await⭐ 使用回调函数库⭐模块化⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端…...
什么是Lambda表达式?
Lambda表达式是Java 8引入的一个重要特性,用于简化函数式编程中的匿名函数的定义和使用。它可以被视为一种轻量级的匿名函数,可以作为参数传递给方法或存储在变量中。 Lambda表达式的语法形式如下: (parameters) -> expression 或 (para…...

公式trick备忘录
增大不同class feature之间的距离用hinge loss 相关, similarity learning, svm https://www.youtube.com/watch?vQtAYgtBnhws https://www.youtube.com/watch?vbM4_AstaBZo&t286s...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...