【设计模式】 模板方法模式介绍及C代码实现

2023-11-09

【设计模式】 模板方法模式介绍及C代码实现

背景

  在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
  比如你要从北京去上海出差,出差的工作是不变的,但是使用的交通工具却有不同的方式,可能有火车、可能飞机、可能开车。如果写程序实现,则每次都要写一个不同的类,并且类中实现功能几乎一样,大量重复的逻辑,相信你已经闻到这里面的一些坏味道了。 这种整体功能稳定,但是子步骤经常改变的需求,就可以使用模板方法设计模式来优化。

定义

那什么是模板方法设计模式?

  模板方法设计模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。这样就使得子类可以不改变一个算法的结构即可重定义(override 重写)该算法的某些特定步骤。

  模板方法模式的主要思想是基于“好莱坞原则”,即“不要打电话给我们,我们会打电话给你”。这意味着在模板方法模式中,父类定义了一个算法框架,但是具体的实现由子类决定。子类可以通过继承父类,并重写父类的某些方法来实现自己的具体实现。

  模板方法模式通常由两个部分组成:抽象父类和具体子类。抽象父类定义了一个算法框架,其中包含了一些抽象方法和具体方法。抽象方法由子类实现,具体方法由父类实现。具体子类通过重写抽象方法来实现自己的具体实现,从而完成整个算法。

应用场景

模板方法设计模式主要应用在以下场景:

  • 当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式。模板方法将整个算法转换为一系列独立的步骤, 以便子类能对其进行扩展, 同时还可让超类中所定义的结构保持完整。

  • 当多个类的算法除一些细微不同之外几乎完全一样时,可以使用该模式。 但其后果就是, 只要算法发生变化, 你就可能需要修改所有的类。在将算法转换为模板方法时, 你可将相似的实现步骤提取到超类中以去除重复代码。 子类间各不同的代码可继续保留在子类中。

模式结构

在这里插入图片描述

抽象类 (Abstract­Class) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为 抽象类型, 也可以提供一些默认实现。

具体类 (Concrete­Class) 可以重写所有步骤, 但不能重写模板方法自身。

实现步骤

模板方法设的主要实现步骤:

  1. 分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同。

  2. 创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final最终修饰模板方法以防止子类对其进行重写。

  3. 虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法。

  4. 可考虑在算法的关键步骤之间添加钩子。

  5. 为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。

C语言代码示例

  其实设计模式是一种与编程语言无关的设计思想,但是其中很重要的思想就是面向对象,所以在面向对象的语言,比如C++、Java中实现起来就非常顺手,但因为我本人是C语言出身,并且作为主要编程语言的,所以就使用了C语言来实现模板方法的设计模式。

在C语言中,我们可以通过函数指针和结构体来实现模板模式。下面是一个示例:

#include <stdio.h>

// 抽象类,定义算法框架
typedef struct {
    void (*step1)(void);
    void (*step2)(void);
    void (*step3)(void);
    void (*run)(void);
} Algorithm;

// 具体子类,实现具体步骤
typedef struct {
    Algorithm super;
    int data;
} MyAlgorithm;

void my_algorithm_step1(void) {
    MyAlgorithm* my_algorithm = (MyAlgorithm*)Algorithm_GetThis();
    printf("MyAlgorithm Step 1 with data %d\n", my_algorithm->data);
}

void my_algorithm_step2(void) {
    MyAlgorithm* my_algorithm = (MyAlgorithm*)Algorithm_GetThis();
    printf("MyAlgorithm Step 2 with data %d\n", my_algorithm->data);
}

void my_algorithm_step3(void) {
    MyAlgorithm* my_algorithm = (MyAlgorithm*)Algorithm_GetThis();
    printf("MyAlgorithm Step 3 with data %d\n", my_algorithm->data);
}

void my_algorithm_run(void) {
    Algorithm* algorithm = Algorithm_GetThis();
    algorithm->step1();
    algorithm->step2();
    algorithm->step3();
}

// 定义抽象类的构造函数
Algorithm* Algorithm_Create(void) {
    Algorithm* algorithm = (Algorithm*)malloc(sizeof(Algorithm));
    algorithm->step1 = NULL;
    algorithm->step2 = NULL;
    algorithm->step3 = NULL;
    algorithm->run = NULL;
    return algorithm;
}

// 定义具体子类的构造函数
MyAlgorithm* MyAlgorithm_Create(int data) {
    MyAlgorithm* my_algorithm = (MyAlgorithm*)malloc(sizeof(MyAlgorithm));
    my_algorithm->super.step1 = my_algorithm_step1;
    my_algorithm->super.step2 = my_algorithm_step2;
    my_algorithm->super.step3 = my_algorithm_step3;
    my_algorithm->super.run = my_algorithm_run;
    my_algorithm->data = data;
    return my_algorithm;
}

// 定义获取this指针的函数,用于将函数指针转换为正确的类型
Algorithm* Algorithm_GetThis(void) {
    // 这里假设调用者已经将this指针保存在全局变量中
    // 实际使用时应该根据具体情况修改这个函数的实现
    return (Algorithm*)this;
}

int main() {
    // 创建一个MyAlgorithm的实例
    MyAlgorithm* my_algorithm = MyAlgorithm_Create(42);

    // 调用run函数运行算法
    this = (void*)my_algorithm; // 将this指针保存在全局变量中
    my_algorithm->super.run();

    // 释放资源
    free(my_algorithm);

    return 0;
}

  在这个例子中,我们定义了一个抽象类 Algorithm,它包含了算法的框架。step1、step2、step3和run这些成员变量都是函数指针,用来定义算法的具体步骤。

  我们还定义了一个具体子类 MyAlgorithm,它继承了 Algorithm,并实现了具体的步骤。这里我们添加了一个 data 成员变量,用于在每个步骤中输出一些信息。

  在具体子类的构造函数 MyAlgorithm_Create 中,我们将 MyAlgorithm 的各个成员变量初始化为具体的函数实现。在 run 函数中,我们按照 Algorithm 的算法框架依次调用各个步骤。

  最后,在 main 函数中,我们创建了一个 MyAlgorithm 的实例,并调用了它的 run 函数运行算法。注意到我们使用了一个全局变量 this 来保存当前的 this 指针,用于在每个步骤中获取 MyAlgorithm 实例的成员变量。实际使用时,我们应该根据具体情况修改这个实现。

  可以说模板方法模式是在开发过程中还是非常常见并且有用的,它可以让我们轻松地定义算法框架,并将具体的实现延迟到子类中。在 C 语言中,我们主要可以使用函数指针和结构体来实现模板模式。

总结

  模板方法模式的优点在于可以封装算法的骨架,让子类专注于具体实现细节。这样可以使得代码具有更好的可维护性、可读性和可扩展性。此外,模板方法模式还能够在不改变算法框架的情况下,扩展算法的功能,从而满足不同的业务需求。

  比如你可以只重写工程中一个大型算法中的特定部分,而不修改其他部分,使得算法其他部分修改对其所造成的影响减小。也可以将重复代码提取到一个超类中。

  总之,模板方法模式是一种非常实用的设计模式,它可以让我们轻松地定义算法框架,并将具体的实现延迟到子类中。这种模式可以提高代码的重用性和可维护性,是面向对象编程中必不可少的一种设计思想。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【设计模式】 模板方法模式介绍及C代码实现 的相关文章

  • istio安装实践

    启动minikube istio的实验环境部署在minikube上 首先启动minikube minikube安装参考上一博客 minikube start vm driver kvm2 extra config controller ma
  • 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数 reference counting 基本思想是将销毁对象的职责从客户端代码转移到对象本身 对象跟踪记录自身当前被引用的数目 在引用计数达到零时自行销毁 换句话说 对象不再被使用时自行销毁 引用计数和执行速度之间的关系是与上下文紧
  • 基于STM32实现MQTT

    最近一个项目中要用到MQTT或者CoAP 比较了两个的优缺点后 最后选择了MQTT 由于是第一次接触这个协议 在学习中遇到了不少的坑 所以分享出来 第一次写博客 有错勿怪 1 MQTT协议 MQTT Message Queuing Tele
  • springboot项目安装SSL证书实现HTTPS访问,以若依框架为例

    首先下载证书 网上有很多免费的 也可以自己购买并域名然后会对应提供证书 以腾讯云为例 下载下来的解压后如图 项目里我们操作有 1 在resource中新建cert文件 并将证书中的Tomcat文件夹中的两个文件导入到cert文件夹中 2 在
  • qt子进程和父进程读写数据通信

    进程A 例如主程序 创建了一个进程 B 这个B就称为A的子进程 而A称为B的父进程 这也称为进程间通信 有多种方式 TCP IP Local Server Socket 共享内存 D Bus Unix库 QProcess 会话管理 这里 因

随机推荐

  • idea插件下载

    一 请求idea 插件网站 JetBrains Marketplace 二 输入actiBPM 三 点击get 四 选择版本 下载 五 在idea 中选择 Install plugin from disk 六 选择刚刚下载插件的目录 点击O
  • springboot后端写接口(入门)

    总结 controller展示 定义接口路径和调用service service 处理业务逻辑 数据库数据 mapper定义操作数据库动作 命名 mapper xml执行mapper里定义的动作的sql语句 与数据库交互 entity 定义
  • 苹果电脑上几款不错的cad绘图软件

    说起3cad绘图 很多人第一反应就是AutoCAD AutoCAD是一款用于Mac平台上的二维绘图 详细绘制 设计文档和基本三维的设计软件 现已经成为国际上广为流行的绘图工具 除了AutoCAD以及lt以外 还有不错的cad绘图软件 下面的
  • 穷举数组所有子集

    最终子序列的个数 Math pow 2 n 因为每个位置都有显示或者不显示两种可能 一共n个位置 所以是2的n次方 例如数组 a b c 每一个子序列都可以看成是数组中每个位置是否显示 1 显示 0 不显示 那么我们可以想到有一下组合 0
  • sqli-labs第八关(布尔盲注)

    第八关看标题可知此关可以使用布尔盲注 布尔盲注我的理解是页面不会回显错误 但是注入等式或不等式时通过页面的反应能判断出注入中算式的true或false 布尔盲注的步骤如下 获取数据库长度 获取数据库名 获取数据库表 获取表中字段 获取表中数
  • React-Hooks源码深度解读

    useState 解析 useState 使用 通常我们这样来使用 useState 方法 function App const num setNum useState 0 const add gt setNum num 1 return
  • RobotFramework环境配置二十五:屏幕截图问题(滚动屏幕)

    屏幕截图问题 滚动屏幕 目的 Selenium2Library 屏幕截图无法保存全屏 需要让屏幕滚动到目标元素的位置 实现 Execute Javascript 一 用例 选卡中心选择课程测试 登录 进入 选卡中心 选择课程 检测元素 期望
  • Python+selenium+PIL实现网页自动截图

    欢迎来到Python办公自动化专栏 Python处理办公问题 解放您的双手 博客主页 一晌小贪欢的博客主页 该系列文章专栏 Python办公自动化专栏 文章作者技术和水平有限 如果文中出现错误 希望大家能指正 欢迎各位佬关注 背景 最近接到
  • 【Matlab优化预测】鲸鱼算法优化SVM预测【含源码 1377期】

    一 代码运行视频 哔哩哔哩 Matlab优化预测 鲸鱼算法优化SVM预测 含源码 1377期 二 matlab版本及参考文献 1 matlab版本 2014a 2 参考文献 1 刘沛津 胡冀飞 贺宁 曹进 徐军昶 改进鲸鱼算法优化LSSVM
  • Android中WebView简介

    1 WebView简介 WebView在Android平台上是一个特殊的View 基于webkit引擎 展示web页面的控件 app中显示的是一张网页 提供了网页的前进 后退 放大 缩小 搜索 WebView在低版本和高版本分别采用不同的
  • 探索短视频小程序/小年糕

    短视频小程序的兴起 为创作者提供了一个全新的平台 让他们能够以更专业的方式展现自己的作品 这种创作形式不仅要求作品内容足够精彩还需要有深度的思考和逻辑性的呈现 本文将探索短视频小程序的专业与深度的创作之道 帮助创作者更好地发挥自己的才华 一
  • Angular 表单状态及校验

    在angular框架中 表单状态的含义 valid 校验成功 invalid 校验失败 pending 表单正在提交过程中 pristine 数据依然处于原始状态 字段没有被修改过 dirty 数据已经变脏了 被用户修改过了 touched
  • 【 C++ 】map、multimap的介绍和使用

    目录 1 map map的介绍 map的定义 insert插入函数 map的迭代器 运算符重载 find查找函数 erase删除函数 其它函数 总结 2 multimap multimap的介绍 multimap的使用 1 map map的
  • Arduino基础篇(五)-- 如何快速上手串口通信(Serial)

    文章目录 1 基础篇 1 1 通信基础 2 串口通信 2 1 Arduino串口的硬件结构 2 2 串口工作原理 2 3 硬件串口通信 2 4 软件模拟串口通信 1 基础篇 1 1 通信基础 1 并行通信 通过输入 输出端口在 Arduin
  • C#学习笔记 异步操作

    同步操作 默认情况下我们的代码都是同步操作 这种情况下 所有的操作都在同一个线程中 如果遇到需要长时间执行的操作或者是一个IO操作 那么代码可能会阻塞比较长的时间 在阻塞的这段时间里 无法进行其他工作 这是很不好的 这里是一个同步操作的例子
  • ML Introduction

    Task of ML Supervised Learning Classification and regression Unsepervised Learning Clustering Density Estimation Reducti
  • 【数据分析】基于时间序列的预测方法

    时间序列预测 目录 时间序列预测 1 时间序列介绍 2 原始数据集 3 导入数据 4 检测时间序列的平稳性 5 如何使时间序列平稳 5 1 估计和消除趋势 5 1 1 对数转换 5 1 2 移动平均 5 2 消除趋势和季节性 5 2 1 差
  • 利用yoloV3模型进行训练和预测

    学习目标 熟悉TFRecord文件的使用方法 知道YoloV3模型结构及构建方法 知道数据处理方法 能够利用yoloV3模型进行训练和预测 1 TFrecord文件 该案例中我们依然使用VOC数据集来进行目标检测 不同的是我们要利用tfre
  • PAT (Advanced Level) Practice 题目集合(1001 ~ 1050)(正在更新)

    1001 A B Format 20 分 题目大意 计算a b 结果按照西方的那种写数字的方式输出 从三个数一个逗号那种 include
  • 【设计模式】 模板方法模式介绍及C代码实现

    设计模式 模板方法模式介绍及C代码实现 背景 在软件构建过程中 对于某一项任务 它常常有稳定的整体操作结构 但各个子步骤却有很多改变的需求 或者由于固有的原因 比如框架与应用之间的关系 而无法和任务的整体结构同时实现 比如你要从北京去上海出