C++设计模式_03_模板方法Template Method

news2024/11/23 21:45:20

文章目录

  • 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) 模式 : 通过类继承或者对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击。
  • 从范围(实现手段)来看
    • 类模式处理类与子类的静态关系:更偏重于继承方案
    • 对象模式处理对象间的动态关系:更偏重于组合方案

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 method
    void 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 method
    void 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方法(前后有流程环境才有意义)。

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

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

相关文章

北京融资融券两融账户最低利率怎么申请?哪家券商无门槛!

北京融资融券利率申请可以低至5%或者以下&#xff0c;无门槛5%支持线上办理&#xff0c;股票成本价&#xff0c;只有极少数的券商可以实现。 北京办理融资融券两融业务如果没有找对正确的开户渠道&#xff0c;自己办理是按照券商默认的利率收取&#xff0c;默认利率8.35%&#…

C#文件重命名工具

文章目录 工具背景4个文件介绍RenamesSpecificPrefixFile.exe.configDataSave.txt 工具介绍重命名的存储方式 文件夹介绍源文件夹 结果使用【PDF】/【视频】重名时坚持拷贝 可能的报错 工具背景 你上次选的这三个选项&#xff0c;他会记住&#xff0c;后面再打开就是上次的位置…

使用本地mysql+linux实现mysql主从同步

1.配置linux 保证linux已经安装好了mysql1.1修改该linux配置文件 vim /etc/my.cnf1.2重启linux的mysql systemctl restart mysqld1.3使用账户密码登录linux中的mysql,查看是否配置成功 mysql> show master status;若显示有FIile和Posttion就表示注linux的主节点配置成功…

基于uwb和IMU融合的三维空间定位算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ..........................................................................kkk 0; for E…

【算法题】小红书2023秋招提前批算法真题解析

文章目录 题目来源T1&#xff1a;5900: 【DP】小红书2023秋招提前批-连续子数组最大和5801: 【二分查找】小红书2023秋招提前批-精华帖子解法1——排序滑动窗口解法2——前缀和 二分查找 5000: 【模拟】小红书2023秋招提前批-小红的数组构造解法——数学 5300: 【哈希表】小红…

欧洲电子产品CE认证 CE-EMC认证办理

任何的产品想要在欧洲自由贸易必须通过CE认证&#xff0c;在产品上加贴CE标志。CE标志表示产品已经达到了欧盟指令规定的安全要求;是企业对消费者的一种承诺&#xff0c;增加了消费者对产品的信任程度。 贴有CE标志的产品将降低在欧洲市场上销售的风险。这些风险包括&#xff1…

C++11新特性③ | 可变参数模板介绍

目录 1、引言 2、可变参数模板函数 2.1、可变参数模板函数的定义 2.2、参数包的展开 3、可变参数模板类 3.1、继承方式展开参数包 3.2、模板递归和特化方式展开参数包 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff…

RabbitMQ: return机制

1. Return机制 Confirm只能保证消息到达exchange&#xff0c;无法保证消息可以被exchange分发到指定queue。 而且exchange是不能持久化消息的&#xff0c;queue是可以持久化消息。 采用Return机制来监听消息是否从exchange送到了指定的queue中 2.Java的实现方式 1.导入依赖 &l…

基于googlenet网络的动物种类识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................. % 获取输入层的尺寸 Inp…

异步编程 - 13 高性能线程间消息传递库 Disruptor

文章目录 Disruptor概述Disruptor中的核心术语Disruptor 流程图 Disruptor的特性详解基于Disruptor实现异步编程 Disruptor概述 Disruptor是一个高性能的线程间消息传递库&#xff0c;它源于LMAX对并发性、性能和非阻塞算法的研究&#xff0c;如今构成了其Exchange基础架构的核…

NIFI使用InvokeHTTP发送http请求

说明 这里介绍四种平时常用的http请求方法&#xff1a;GET、POST、PUT、DELETE。 在官方的介绍文档中关于InvokeHTTP处理器的描述是这么说的&#xff1a; An HTTP client processor which can interact with a configurable HTTP Endpoint. The destination URL and HTTP Met…

java 企业工程管理系统软件源码 自主研发 工程行业适用

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…

网工内推 | 云架构运维、网络工程师,base北京,最高20k

01 协合新能源 招聘岗位&#xff1a;IT运维工程师 职责描述&#xff1a; 1、对集团内使用云计算架构&#xff08;Kubernetes&#xff09;的系统进行规划、运维及管理相关工作。 2、对集团数据中心系统的大数据基础架构&#xff08;Cloudera Distribution Hadoop&#xff09;的…

【办公类-16-06】20230901大班运动场地分配表-斜线排列、5天循环、不跳节日,手动修改节日”(python 排班表系列)

背景需求&#xff1a; 大班组长发来一个“运动排班”的需求表&#xff1a;“就是和去年一样的每个班的运动排班&#xff0c;就因为今年大班变成7个班&#xff0c;要重新做一份&#xff0c;不然我就用去年的那份了&#xff08;8个大班排班&#xff09;” &#xff08;拆了中8班…

STM32定时器的One Pulse Mode,OPM应用

文章目录 OPM应用1-精准延时应用2-精准定时 OPM T IMx_CR1的OPM位 位 3 OPM&#xff1a;单脉冲模式 (One-pulse mode) 0&#xff1a;计数器在发生更新事件时不会停止计数 1&#xff1a;计数器在发生下一更新事件时停止计数&#xff08;将 CEN 位清零&#xff09; 应用1-精准延时…

光学显微镜算法(OMA)(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

期权开户必读:费用、保证金和稳定性安全性必须兼备

期权开户的核心是判断50ETF方向&#xff0c;上涨下跌都能赚钱&#xff0c;其次选择0门槛期权平台要考量期权手续费和安全性是第一位&#xff0c;下文为大家科普期权开户的核心&#xff1a;费用、保证金和稳定性安全性必须兼备的知识点。本文来自 &#xff1a;期权酱 一、期权开…

如何把Android Framework学彻底?一条龙学习

Framework通俗易懂 平时学习 Android 开发的第一步就是去学习各种各样的 API&#xff0c;如 Activity&#xff0c;Service&#xff0c;Notification 等。其实这些都是 Framework 提供给我们的。Framework 层为开发应用程序提供了非常多的API&#xff0c;我们通过调用这些 API …

自然语言处理——数据清洗

一、什么是数据清洗 数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序&#xff0c;包括检查数据一致性&#xff0c;处理无效值和缺失值等。与问卷审核不同&#xff0c;录入后的数据清理一般是由计算机而不是人工完成。 ——百度百科 二、为什么要数据清洗 现实生…

Apipost:你API管理中的得力助手

API管理的难点在哪&#xff1f; 相信无论是前端&#xff0c;还是后端的测试和开发人员&#xff0c;都遇到过这样的困难。不同工具之间数据一致性非常困难、低效。多个系统之间数据不一致&#xff0c;导致协作低效、频繁出问题&#xff0c;开发测试人员痛苦不堪。 开发人员在 …