C++软件设计模式之模板方法模式

news2025/1/7 20:33:51

模板方法模式是面向对象软件设计模式之一,其主要意图是在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些特定步骤。

动机

在软件开发中,常常会遇到这样的情况:一个任务可以被分解为多个步骤来完成,其中有些步骤的实现方法是对所有子类通用的,而有些步骤则需要针对不同的子类有不同的实现方式。模板方法模式正是解决这一问题的有效手段。通过使用模板方法模式,可以将不变的部分代码抽离出来放在基类中,而将可变的部分留给子类去实现。这样不仅减少了代码的重复,而且还能够保证变与不变的部分之间的联系。

意图

模板方法模式的主要意图是定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。这样做可以让子类在不改变算法整体结构的前提下去重写算法的某些特定部分。

适用场合

  1. 不变的算法骨架:当某个抽象类有多个子类,但是它们有共同的算法步骤,只是某些步骤的具体实现不同,可以通过模版方法模式将这些不变的部分提取到抽象类中,子类只需要实现特定的步骤。
  2. 避免代码重复:多个类中若有一段代码完全相同或相似,可以考虑使用模版方法模式来抽取共享的部分,减少代码重复。
  3. 控制子类扩展:模版方法模式可以通过在模版方法中调用“钩子”方法,来控制子类在生命周期中的特定点上能够做什么,不能够做什么。钩子方法在抽象类中通常是空实现,子类可以覆盖这些方法来实现特定功能。

示例

模板方法模式的一个经典示例是咖啡店和茶馆的饮料制作过程。制作咖啡和茶的基本流程大致相同(准备热水、冲泡、倒入杯子、加调料),但具体的步骤(如冲泡的方式、加何种调料)则不同。通过定义一个模版方法来实现这个流程,基类可以控制整个流程的执行顺序,而具体的冲泡和加调料动作则由子类实现。

模板方法模式是一种非常有用的设计模式,在很多框架和库中都有应用。正确地使用模版方法模式可以使代码更加清晰、易于扩展和维护。

面是一个简单的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 Milk

Making Tea...
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon

解释

  1. 基类 Beverage:

    • prepareRecipe() 是模板方法,定义了制作饮料的步骤:煮水、冲泡、倒入杯子、加调料。
    • boilWater() 和 pourInCup() 是所有饮料共有的步骤,因此在基类中实现。
    • brew() 和 addCondiments() 是纯虚函数,需要在子类中实现,因为这些步骤对不同的饮料有不同的实现。
  2. 子类 Coffee 和 Tea:

    • Coffee 子类实现了 brew() 和 addCondiments(),分别表示冲泡咖啡和添加糖和牛奶。
    • Tea 子类实现了 brew() 和 addCondiments(),分别表示泡茶和添加柠檬。
  3. main 函数:

    • 创建 Coffee 和 Tea 对象,并调用它们的 prepareRecipe() 方法,输出了制作咖啡和茶的过程。

适用场合

  • 通用流程,具体步骤不同:当多个子类需要遵循相同的流程,但具体的步骤实现不同,可以使用模板方法模式。例如,不同的支付方式(信用卡、支付宝、微信)可以遵循相同的支付流程,但具体的支付接口和验证方式不同。
  • 避免重复代码:在多个子类中存在相同的逻辑时,可以将这些逻辑提取到基类的模板方法中,避免代码重复。

模板方法模式通过将算法的结构固定,允许子类灵活地实现具体步骤,提供了一种优雅的方式来处理类似的任务。

 模板方法模式经常与其他设计模式协同使用,以解决更复杂的设计问题。常见的协同模式包括策略模式、工厂方法模式和状态模式等。下面我们将重点介绍模板方法模式与策略模式的协同使用,并给出一个C++代码示例。

模板方法模式与策略模式的协同使用

动机

模板方法模式用于定义一个算法的骨架,而将某些步骤的实现延迟到子类中。策略模式用于定义一系列可互换的算法,并将这些算法封装在独立的类中。通过将策略模式与模板方法模式结合,可以在一个模板方法中使用不同的策略,从而提供更大的灵活性。

适用场景
  1. 算法的某些步骤需要根据不同的条件动态选择不同的实现:可以通过策略模式将这些步骤的实现封装在不同的策略类中,然后在模板方法中根据需要选择合适的策略。
  2. 需要在运行时动态改变算法的某些步骤:策略模式允许在运行时动态更换算法,而模板方法模式确保了算法的整体结构不变。

代码示例

假设我们有一个任务是处理数据,不同的处理策略可以根据不同的需求进行选择。我们使用模板方法模式来定义数据处理的骨架,使用策略模式来实现不同的处理策略。

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 data

Using Encrypt Strategy...
Loading data
Encrypting data
Saving data

Using Verify Strategy...
Loading data
Verifying data
Saving data

解释

  1. 策略接口 DataProcessingStrategy:

    • 定义了一个纯虚函数 process(),用于实现数据处理的具体策略。
    • 具体策略 CompressStrategyEncryptStrategy 和 VerifyStrategy 分别实现了数据压缩、加密和校验的策略。
  2. 抽象类 DataProcessor:

    • 包含一个策略对象 strategy,并通过构造函数传递具体的策略对象。
    • 定义了模板方法 processData(),该方法调用了 load()strategy->process() 和 save(),确保数据处理的流程一致。
    • load() 和 save() 是通用的数据加载和保存步骤,可以在子类中根据需要进行扩展。
  3. 具体数据处理器类:

    • CompressDataProcessorEncryptDataProcessor 和 VerifyDataProcessor 分别使用不同的策略对象初始化 DataProcessor
  4. 客户端代码:

    • 创建不同的数据处理器对象,并调用 processData() 方法来处理数据,输出了不同的处理策略的结果。

通过这种方式,模板方法模式和策略模式的结合提供了更大的灵活性,允许在运行时动态选择不同的数据处理策略,同时保持数据处理流程的一致性。

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

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

相关文章

计算机毕业设计Python电商品推荐系统 商品比价系统 电商比价系统 商品可视化 商品爬虫 机器学习 深度学习 京东爬虫 国美爬虫 淘宝爬虫 大数据

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

BBP飞控板中的坐标系变换

一般飞控板中至少存在以下坐标系&#xff1a; 陀螺Gyro坐标系加速度计Acc坐标系磁强计Mag坐标系飞控板坐标系 在BBP飞控板采用的IMU为同时包含了陀螺&#xff08;Gyro&#xff09;及加速度计&#xff08;Acc&#xff09;的6轴传感器&#xff0c;故Gyro及Acc为同一坐标系。同时…

企业网络综合组网

1 概述 2 网络需求分析 2.1企业需求分析 公司规模 员工规模&#xff1a;200人&#xff0c;其中包括技术研发人员&#xff0c;市场营销人员&#xff0c;运营管理人员&#xff0c;客户服务人员等。部门数量&#xff1a;19个部门&#xff0c;包括财务部&#xff0c;人力资源部…

【沉默的羔羊心理学】汉尼拔的“移情”游戏:操纵与理解的艺术,精神分析学视角下的角色互动

终极解读《沉默的羔羊》&#xff1a;弗洛伊德精神分析学视角下的深层剖析 关键词 沉默的羔羊弗洛伊德精神分析学角色心理意识与潜意识性别与身份 弗洛伊德精神分析学简介 弗洛伊德的精神分析学是心理学的一个重要分支&#xff0c;主要关注人类行为背后的无意识动机和冲突。…

Qt窗口获取Tftpd32_svc服务下载信息

前言 一个由Qt开发的Windows小工具需要布置Tftp协议服务端来支持设备下载数据&#xff0c;并显示下载列表&#xff08;进度、下载源等&#xff09;。 考虑开发方便&#xff0c;优先使用了Qtftp方案&#xff0c;经测试发现&#xff0c;不够稳定&#xff0c;会有下载超时的情况&a…

合合信息亮相CSIG AI可信论坛,全面拆解AI视觉内容安全的“终极防线”

合合信息亮相CSIG AI可信论坛&#xff0c;全面拆解视觉内容安全的“终极防线”&#xff01; &#x1f42f; AI伪造泛滥&#xff0c;我们还能相信“眼见为实”吗&#xff1f; 近期&#xff0c;由中国图象图形学学会主办的CSIG青年科学家会议 AI可信论坛在杭州成功举办。本次论…

Bash Shell的操作环境

目录 1、路径与指令搜寻顺序 2、bash的进站&#xff08;开机&#xff09;与欢迎信息&#xff1a;/etc/issue&#xff0c;/etc/motd &#xff08;1&#xff09;/etc/issue &#xff08;2&#xff09;/etc/motd 3、bash的环境配置文件 &#xff08;1&#xff09;login与non-…

如何在没有 iCloud 的情况下将联系人从 iPhone 传输到 iPhone

概括 近期iOS 13.5的更新以及苹果公司发布的iPhone SE在众多iOS用户中引起了不小的轰动。此外&#xff0c;不少变化&#xff0c;如暴露通知 API、Face ID 增强功能以​​及其他在 COVID-19 期间与公共卫生相关的新功能&#xff0c;吸引了 iPhone 用户尝试新 iPhone 并更新到最…

网站设计总结后期维护与更新的重要性

当我们谈论网站设计时&#xff0c;往往会聚焦在初始阶段的创意和实现上。然而&#xff0c;一旦网站建成并上线&#xff0c;后期维护与更新的重要性就显得尤为突出。一个网站的成功不仅取决于其初始设计&#xff0c;更在于持续的维护与更新。 首先&#xff0c;后期维护能够确保网…

Android NDK开发实战之环境搭建篇(so库,Gemini ai)

文章流程 音视频安卓开发首先涉及到ffmpeg编译打包动态库&#xff0c;先了解动态库之间的cpu架构差异性。然后再搭建可运行的Android 环境。 So库适配 ⽇常开发我们经常会使⽤到第三库&#xff0c;涉及到底层的语⾳&#xff0c;视频等都需要添加so库。⽽so库的体积⼀般来说 ⾮…

数据仓库中的指标体系模型介绍

数据仓库中的指标体系介绍 文章目录 数据仓库中的指标体系介绍前言什么是指标体系指标体系设计有哪些模型?1. 指标分层模型2. 维度模型3. 指标树模型4. KPI&#xff08;关键绩效指标&#xff09;模型5. 主题域模型6.平衡计分卡&#xff08;BSC&#xff09;模型7.数据指标框架模…

unity学习11:地图相关的一些基础

目录 1 需要从 unity的 Asset Store 下载资源 1.1 下载资源 1.2 然后可以从 package Manager 里选择下载好的包&#xff0c;import到项目里 2 创建地形 2.1 创建地形 2.2 地形 Terrain大小 2.3 各种网格的尺寸大小 2.4 比较这个地形尺寸和创建的其他物体的大小对比 3 …

jenkins入门--安装jenkins

下载地址https://www.jenkins.io/ jdk 安装 &#xff1a;Jenkins需要安装对应版本的jdk,我在安装过程中显示需要21,17 Java Downloads | Oracle jenkins安装过程参考全网最清晰Jenkins安装教程-windows_windows安装jenkins-CSDN博客 安装完成后&#xff0c;浏览器输入127.0.…

第0章 机器人及自动驾驶SLAM定位方法全解析及入门进阶学习建议

嗨&#xff0c;各位同学大家好&#xff01;笔者自985硕士毕业后&#xff0c;在机器人算法领域已经深耕 7 年多啦。这段时间里&#xff0c;我积累了不少宝贵经验。本专栏《机器人工程师带你从零入门SLAM》将结合下面的SLAM知识体系思维导图及多年的工作实战总结&#xff0c;将逐…

springCloud 脚手架项目功能模块:Java分布式锁

文章目录 引言分布式锁产生的原因:集群常用的分布式锁分布式锁的三种实现方式I ZooKeeper 简介zookeeper本质上是一个分布式的小文件存储系zookeeper特性:全局数据一致性ZooKeeper的应用场景分布式锁(临时节点)II 基于ZooKeeper 实现一个排他锁创建锁获取锁释放锁Apache Zo…

如何配置【Docker镜像】加速器+【Docker镜像】的使用

一、配置Docker镜像加速器 1. 安装/升级容器引擎客户端​ 推荐安装1.11.2以上版本的容器引擎客户端 2. 配置镜像加速器​ 针对容器引擎客户端版本大于1.11.2的用户 以root用户登录容器引擎所在的虚拟机 修改 "/etc/docker/daemon.json" 文件&#xff08;如果没有…

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执行测试 三、自定义…

go项目zero框架中用gentool解决指定MYSQL表生成结构体被覆盖的解决方案

在使用 GoZero 框架进行项目开发时&#xff0c;gentool 是一个非常方便的工具&#xff0c;它可以根据数据库表结构自动生成 Go 语言结构体和其他相关文件。然而&#xff0c;在使用 gentool 生成结构体时&#xff0c;可能会遇到一个问题&#xff1a;如果多次运行 gentool&#x…

深入Android架构(从线程到AIDL)_11 线程之间的通信架构

目录 5、 线程之间的通信架构 认识Looper与Handler对象 主线程丢信息给自己 子线程丢信息给主线程 替子线程诞生Looper与MQ 5、 线程之间的通信架构 认识Looper与Handler对象 当主线程诞生时&#xff0c;就会去执行一个代码循环(Looper)&#xff0c;以便持续监视它的信息…

今日自动化编辑部今日自动化杂志社2024年第19期部分目录

智能控制技术 无人机视觉支持下的输电线路安全距离巡检系统探究 贺凌飞 王骋昊1-2,36 基于虚拟现实技术的安全警示系统设计 黄奇 李光辉 徐奎 许兆辉3-5 火焰自动焊接技术对泄漏率的影响研究 孙天鸽5-7 PLC在闸门自动化控制系统中的应用 黎芳8-9,23 智能控制算法在二次供水系统…