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

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

动机

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

意图

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

适用场合

  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() 方法来处理数据,输出了不同的处理策略的结果。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Yolo11 基于DroneVehicle数据集的无人机视角下车辆目标检测

1、关于DroneVehicle数据集介绍 DroneVenicle数据集是由天津大学收集、标注的大型无人机航拍车辆数据集。 DroneVehicle 数据集由无人机采集的共 56,878 幅图像组成&#xff0c;其中一半为 RGB 图像&#xff0c;其余为红外图像。我们对五个类别进行了带有方向性边界框的丰富标…

Requests库01|使用Requests库发送 get/post/put/delete请求

学习目标&#xff1a; 能够使用Requests库发送 get/post/put/delete请求&#xff0c;获取响应状态码、数据能够使用UnitTest管理测试用例。 目录 一、Requests库安装和简介 二、设置http请求语法&#xff08;重要&#xff09; 三、应用案例&#xff08;重要&#xff09; …

[有用教程]从 Pixel 快速传输到 Android

概括 更换新手机很容易&#xff0c;但数据迁移却不容易。目前&#xff0c;用户喜欢转换品牌&#xff0c;应用市场上的转换工具也越来越多。然而&#xff0c;它们并不都是安全的。因此&#xff0c;选择一款简单、安全的迁移工具至关重要。 今天我们将讨论如何从 Pixel 转移到 …

【蓝桥杯研究生组】第15届Java试题答案整理

D 题 试题 D: 商品库存管理 时间限制: 3.0s 内存限制: 512.0MB 本题总分&#xff1a;10 分 【问题描述】 在库存管理系统中&#xff0c;跟踪和调节商品库存量是关键任务之一。小蓝经营的仓库中存有多种商品&#xff0c;这些商品根据类别和规格被有序地分类并编号&#xff0c;…

BUUCTF sqli-labs 1

这里就是单纯的找一下flag在哪&#xff0c;通关整个靶场在sql注入分区&#xff0c;虽然还没有通关。 这里要先看一下数据库都有哪些&#xff0c;用到语句&#xff1a;?id-1 union select 1,(select group_concat(schema_name) from information_schema.schemata),3-- 发现这个…

python实现自动登录12306抢票 -- selenium

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 python实现自动登录12306抢票 -- selenium 前言其实网上也出现了很多12306的代码&#xff0c;但是都不是最新的&#xff0c;我也是从网上找别人的帖子&#xff0c;看B站视频&…

Spring自动化创建脚本-解放繁琐的初始化配置!!!(自动化SSM整合)

一、实现功能(原创&#xff0c;转载请告知) 1.自动配置pom配置文件 2.自动识别数据库及数据表&#xff0c;创建Entity、Dao、Service、Controller等 3.自动创建database.properties、mybatis-config.xml等数据库文件 4.自动创建spring-dao.xml spring-mvc.xml …

[微服务] - MQ高级

在昨天的练习作业中&#xff0c;我们改造了余额支付功能&#xff0c;在支付成功后利用RabbitMQ通知交易服务&#xff0c;更新业务订单状态为已支付。 但是大家思考一下&#xff0c;如果这里MQ通知失败&#xff0c;支付服务中支付流水显示支付成功&#xff0c;而交易服务中的订单…

MySQL(面试题 - 同类型归纳面试题)

目录 一、MySQL 数据类型 1. 数据库存储日期格式时&#xff0c;如何考虑时区转换问题&#xff1f; 2. Blob和text有什么区别&#xff1f; 3. mysql里记录货币用什么字段类型比较好&#xff1f; 4. MySQL如何获取当前日期&#xff1f; 5. 你们数据库是否支持emoji表情存储…

aws(学习笔记第二十一课) 开发lambda应用程序

aws(学习笔记第二十一课) 开发lambda应用程序 学习内容&#xff1a; lambda的整体概念开发lambda应用程序 1. lambda的整体概念 借助AWS Lambda&#xff0c;无需预置或管理服务器即可运行代码。只需为使用的计算时间付费。借助 Lambda&#xff0c;可以为几乎任何类型的应用进…

【优选算法】查找总价格为目标值的两个商品

链接&#xff1a;LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;利用单调性&#xff0c;使用双指针算法解决问题 1.先从小到大排序 2. sum > t : right--; sum < t : left; sum t : return class Solution {public…

VUE echarts 教程二 折线堆叠图

VUE echarts 教程一 折线图 import * as echarts from echarts;var chartDom document.getElementById(main); var myChart echarts.init(chartDom); var option {title: {text: Stacked Line},tooltip: {trigger: axis},legend: {data: [Email, Union Ads, Video Ads, Dir…

bilibili 哔哩哔哩小游戏SDK接入

小游戏的文档 简介 bilibili小游戏bilibili小游戏具有便捷、轻量、免安装的特点。游戏包由云端托管&#xff0c;在哔哩哔哩APP内投放和运行&#xff0c;体验流畅&#xff0c;安全可靠。https://miniapp.bilibili.com/small-game-doc/guide/intro/ 没想过接入这个sdk比ios还难…

2024年中国新能源汽车用车发展怎么样 PaperGPT(二)

用车趋势深入分析 接上文&#xff0c;2024年中国新能源汽车用车发展怎么样 PaperGPT&#xff08;一&#xff09;-CSDN博客本文将继续深入探讨新能源汽车的用车强度、充电行为以及充电设施的现状。 用车强度 月均行驶里程&#xff1a;2024年纯电车辆月均行驶超过1500公里&…

自从学会Git,感觉打开了一扇新大门

“同事让我用 Git 提交代码&#xff0c;我居然直接把项目文件压缩发过去了……”相信很多初学者都经历过类似的窘境。而当你真正掌握 Git 时&#xff0c;才会发现它就像一本魔法书&#xff0c;轻松解决代码管理的种种难题。 为什么 Git 能成为程序员的标配工具&#xff1f;它究…

简易屏幕共享工具-基于WebSocket

前面写了两个简单的屏幕共享工具&#xff0c;不过那只是为了验证通过截屏的方式是否可行&#xff0c;因为通常手动截屏的频率很低&#xff0c;而对于视频来说它的帧率要求就很高了&#xff0c;至少要一秒30帧率左右。所以&#xff0c;经过实际的截屏工具验证&#xff0c;我了解…

yakit-靶场-高级前端加解密与验签实战(for嵌套纯享版)

高级前端加解密与验签实战 一、前端验证签名&#xff08;验签&#xff09;表单&#xff1a;HMAC-SHA256 使用hmac-sha256的十六进制key值可以加密 与页面加密后的值相同 热加载&#xff1a; encryptData func(p) { //sha256key值key codec.DecodeHex("313233343132333…

嵌入式入门Day35

网络编程 Day2 套接字socket基于TCP通信的流程服务器端客户端TCP通信API 基于UDP通信的流程服务器端客户端 作业 套接字socket socket套接字本质是一个特殊的文件&#xff0c;在原始的Linux中&#xff0c;它和管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;信号等…

模仿微信小程序wx.showModal自定义弹窗,内容可以修改

实现以下弹框样式功能 1.在components新建一个文件showModel.wpy作为组件&#xff0c;复制下面代码 <style lang"less" scoped> .bg_model {display: flex;justify-content: center;align-items: center;// 弹框背景.bg_hui {width: 100%;height: 100%;posi…

如何在 Ubuntu 22.04 上安装并开始使用 RabbitMQ

简介 消息代理是中间应用程序&#xff0c;在不同服务之间提供可靠和稳定的通信方面发挥着关键作用。它们可以将传入的请求存储在队列中&#xff0c;并逐个提供给接收服务。通过以这种方式解耦服务&#xff0c;你可以使其更具可扩展性和性能。 RabbitMQ 是一种流行的开源消息代…