目录

前言

一、模板方法模式的核心思想

二、模板方法模式的结构组成

1. 抽象类(Abstract Class)

2. 具体子类(Concrete Class)

三、C++ 实现示例:咖啡与茶的制作流程

步骤 1:定义抽象类(饮料基类)

步骤 2:实现具体子类(咖啡和茶)

步骤 3:测试代码

四、模板方法模式的优缺点(C++ 视角)

优点

缺点

五、C++ 中模板方法模式的应用场景

六、C++ 中模板方法模式的注意事项

七、总结


前言

在面向对象设计中,我们经常会遇到这样的场景:多个类实现相似的流程,但部分步骤存在差异。如果为每个类重复编写相同的流程代码,不仅会导致冗余,还会增加维护成本。模板方法模式正是为解决这类问题而生的 —— 它通过定义统一的流程骨架,将可变步骤延迟到子类实现,完美平衡了流程标准化细节差异化

本文将结合 C++ 代码,深入解析模板方法模式的原理、实现与应用。

一、模板方法模式的核心思想

模板方法模式是一种经典的行为型设计模式,其核心可以概括为:“定义算法骨架,延迟具体实现”

想象一个场景:制作咖啡和茶的流程高度相似(煮水→冲泡→装杯→加调料),但 “冲泡” 和 “加调料” 的具体操作不同。模板方法模式会将这些相同的流程步骤抽象到父类,而将不同的步骤声明为抽象方法,由子类(咖啡、茶)各自实现。

用 C++ 的视角来看,这种模式通过抽象类 + 继承实现:

  • 抽象类负责定义 “流程骨架”(模板方法);
  • 子类负责实现 “可变细节”(抽象方法)。

二、模板方法模式的结构组成

模板方法模式主要包含两个角色,在 C++ 中通常通过类层次结构实现:

1. 抽象类(Abstract Class)

  • 模板方法(Template Method):一个非虚成员函数(通常用final修饰,防止子类重写),定义算法的步骤顺序,调用其他方法(包括抽象方法和具体方法)。
  • 抽象方法(Primitive Operations):纯虚函数,由子类实现的可变步骤。
  • 具体方法(Concrete Methods):普通成员函数,算法中固定不变的步骤(子类无需修改)。
  • 钩子方法(Hook Methods):虚函数(有默认实现),子类可选择性重写,用于控制模板流程的分支(可选)。

2. 具体子类(Concrete Class)

  • 继承抽象类,实现所有纯虚的抽象方法;
  • 可选重写钩子方法,调整流程的执行逻辑。

三、C++ 实现示例:咖啡与茶的制作流程

下面我们用 C++ 实现一个经典案例:咖啡和茶的制作。两者的流程相似,但 “冲泡” 和 “加调料” 的步骤不同,适合用模板方法模式封装。

步骤 1:定义抽象类(饮料基类)

首先创建抽象类Beverage,它包含制作饮料的流程骨架和相关方法:

// Beverage.h
#ifndef BEVERAGE_H
#define BEVERAGE_H#include <iostream>
using namespace std;class Beverage {
public:// 模板方法:定义制作流程的骨架,用final防止子类修改(C++11及以上支持)void prepareRecipe() final {boilWater();       // 固定步骤:煮水brew();            // 抽象方法:冲泡(子类实现)pourInCup();       // 固定步骤:倒入杯子addCondiments();   // 抽象方法:加调料(子类实现)if (customerWantsCondiments()) {  // 钩子方法:判断是否加额外调料addExtraCondiments();         // 可选步骤:加额外调料}}protected:// 抽象方法:冲泡(纯虚函数,子类必须实现)virtual void brew() = 0;// 抽象方法:加调料(纯虚函数,子类必须实现)virtual void addCondiments() = 0;// 钩子方法:默认返回true(加额外调料),子类可重写virtual bool customerWantsCondiments() {return true;}private:// 具体方法:固定步骤——煮水(子类无需修改)void boilWater() {cout << "煮水至沸腾" << endl;}// 具体方法:固定步骤——倒入杯子(子类无需修改)void pourInCup() {cout << "倒入杯中" << endl;}// 具体方法:可选步骤——加额外调料(固定逻辑)void addExtraCondiments() {cout << "添加额外糖/奶" << endl;}
};#endif // BEVERAGE_H

代码说明

  • prepareRecipe()是模板方法,用final确保子类无法修改流程顺序;
  • brew()addCondiments()是纯虚函数(抽象方法),必须由子类实现;
  • customerWantsCondiments()是钩子方法,提供默认实现,子类可重写以改变流程分支;
  • boilWater()pourInCup()等是具体方法,封装固定步骤,子类无需关心。

步骤 2:实现具体子类(咖啡和茶)

接下来创建CoffeeTea类,继承Beverage并实现抽象方法:

Coffee

// Coffee.h
#ifndef COFFEE_H
#define COFFEE_H#include "Beverage.h"class Coffee : public Beverage {
protected:// 实现抽象方法:咖啡的冲泡逻辑void brew() override {cout << "用沸水冲泡咖啡粉" << endl;}// 实现抽象方法:咖啡的加调料逻辑void addCondiments() override {cout << "加牛奶和糖" << endl;}// 重写钩子方法:咖啡默认不加额外调料bool customerWantsCondiments() override {return false; // 不执行addExtraCondiments()}
};#endif // COFFEE_H

Tea

// Tea.h
#ifndef TEA_H
#define TEA_H#include "Beverage.h"class Tea : public Beverage {
protected:// 实现抽象方法:茶的冲泡逻辑void brew() override {cout << "用沸水浸泡茶叶" << endl;}// 实现抽象方法:茶的加调料逻辑void addCondiments() override {cout << "加柠檬" << endl;}// 不重写钩子方法,使用父类默认逻辑(加额外调料)
};#endif // TEA_H

代码说明

  • CoffeeTea分别实现了brew()addCondiments(),定义各自的差异化步骤;
  • Coffee重写了钩子方法customerWantsCondiments(),返回false以跳过 “加额外调料” 步骤;
  • Tea未重写钩子方法,使用父类默认实现(返回true),因此会执行 “加额外调料”。

步骤 3:测试代码

最后编写测试代码,验证模板方法的执行效果:

// main.cpp
#include "Coffee.h"
#include "Tea.h"int main() {// 制作咖啡Beverage* coffee = new Coffee();cout << "制作咖啡:" << endl;coffee->prepareRecipe();// 制作茶Beverage* tea = new Tea();cout << "\n制作茶:" << endl;tea->prepareRecipe();// 释放资源delete coffee;delete tea;return 0;
}

输出结果

制作咖啡:
煮水至沸腾
用沸水冲泡咖啡粉
倒入杯中
加牛奶和糖制作茶:
煮水至沸腾
用沸水浸泡茶叶
倒入杯中
加柠檬
添加额外糖/奶

结果分析

  • 咖啡和茶都遵循了prepareRecipe()定义的流程骨架;
  • 差异化步骤(brew()addCondiments())按子类实现执行;
  • 钩子方法控制了流程分支:咖啡跳过 “加额外调料”,茶则执行了该步骤。

四、模板方法模式的优缺点(C++ 视角)

优点

  1. 代码复用:将公共流程(如boilWater())抽取到抽象类,减少重复代码;
  2. 符合开闭原则:新增饮料(如奶茶)只需继承Beverage并实现抽象方法,无需修改父类;
  3. 流程可控:父类通过final修饰模板方法,避免子类破坏流程逻辑;
  4. 逻辑清晰:算法的核心步骤在父类中集中体现,便于维护。

缺点

  1. 类数量膨胀:每增加一个具体实现就需要一个子类,可能导致类数量过多;
  2. 继承局限性:C++ 不支持多继承,子类若需复用多个模板流程会受限制;
  3. 耦合性:子类与父类的依赖较强,父类的修改可能影响所有子类。

五、C++ 中模板方法模式的应用场景

  1. 框架设计
    C++ 框架常通过模板方法模式定义核心流程,让用户通过子类定制细节。例如:

    • MFC 框架的Run()方法:定义了消息循环的骨架,用户重写OnDraw()等方法处理具体消息;
    • 测试框架(如 Google Test):Test类定义了测试用例的执行流程,用户实现SetUp()TearDown()定制前后置操作。
  2. 业务流程标准化
    当多个业务场景共享相似流程但细节不同时,例如:

    • 支付流程(创建订单→验证→扣款→回调):不同支付方式(微信 / 支付宝)的 “扣款” 步骤不同;
    • 数据处理流程(读取→解析→过滤→存储):不同数据格式(JSON/XML)的 “解析” 步骤不同。
  3. 钩子方法的灵活使用
    需要根据子类特性动态调整流程时,例如:

    • 日志框架:通过钩子方法控制是否输出调试日志;
    • 游戏 AI:通过钩子方法决定角色在特定条件下的行为分支。

六、C++ 中模板方法模式的注意事项

  1. 模板方法用final修饰
    C++11 及以上支持final关键字,修饰模板方法可防止子类意外修改流程顺序,例如:

    void prepareRecipe() final { ... } // 禁止子类重写
    
  2. 抽象方法与钩子方法的区分

    • 必须实现的步骤用纯虚函数virtual void func() = 0);
    • 可选重写的步骤用虚函数(提供默认实现),即钩子方法。
  3. 避免过度设计
    若流程差异较小,不必强行使用模板方法模式,否则可能增加代码复杂度。

七、总结

模板方法模式是 C++ 中实现 “流程复用 + 细节定制” 的经典模式,它通过抽象类定义算法骨架,用子类填充可变细节,既保证了流程的一致性,又保留了灵活性。

在 C++ 开发中,合理使用模板方法模式可以显著减少重复代码,提高系统的可维护性。但需注意平衡继承带来的耦合性,必要时可结合策略模式(通过组合实现)弥补继承的局限性。

掌握模板方法模式,不仅能写出更优雅的 C++ 代码,更能理解许多 C++ 框架的设计思想 ——“骨架由框架定,细节由开发者填”

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LinkedList 深度解析:核心原理与实践

文章目录 一、底层数据结构与特性 1. 核心数据结构 2. 关键特性 二、核心操作机制解析 1. 添加元素机制 2. 删除元素机制 三、性能关键点分析 1. 时间复杂度对比 2. 空间开销 四、线程安全解决方案 1. 同步包装器 2. 使用并发集合 五、经典面试题解析 1. ArrayList 和 LinkedLi…

Jmeter性能测试之安装及启动Jmeter

1. 安装JDK Jmeter依赖JDK环境,如果电脑没有JDK,需要安装JDK.如下是Jmeter版本与JDK版本对应关系. 2. Jmeter下载安装 下载链接&#xff1a;https://archive.apache.org/dist/jmeter/binaries/ windows下载.zip压缩包Linux下载.tar压缩包 下一步下一步就行 3. 配置环境变…

ShadowKV 机制深度解析:高吞吐长上下文 LLM 推理的 KV 缓存“影子”方案

背景与核心思想简介 在LLM的长上下文推理中&#xff0c;KV Cache成为影响速度和内存的关键因素。每生成一个新token&#xff0c;模型需要对所有先前token的键&#xff08;Key&#xff09;和值&#xff08;Value&#xff09;向量执行自注意力计算。传统方法会将所有过去的K/V向量…

spring-ai整合PGVector实现RAG

背景 最近公司的产品和业务线&#xff0c;要求往ai方向靠拢&#xff0c;在研发各种智能体&#xff0c;整理下最近学习的过程&#xff0c;将一部分内容整理出来&#xff0c;分享给需要的同学。 这篇文章将会提供详细的例子以及踩坑说明。主要内容是整合spring-ai&#xff0c;同…

Git 乱码文件处理全流程指南

一、问题背景与核心目标 1.1 问题描述 在 Git 仓库中发现了一个异常乱码文件&#xff1a; "\001\342\240\025\250\325\3738\f\036\035\006\004\240\002\240\002\b\003\004\340\002\340\002\340\002\034\034\001\001\004:\016\020\001\005\016\016\016\211\266\257\211\266…

JavaScript垃圾回收机制

1.垃圾回收的概念 1.1 什么是垃圾回收机制&#xff1a; GC 即 Garbage Collection &#xff0c;程序工作过程中会产生很多"垃圾"&#xff0c;这些垃圾是程序不用的内存或者是之前用过了&#xff0c;以后不会再用的内存空间&#xff0c;而 GC 就是负责回收垃圾的&…

工业相机选择规则

一、相机分辨率选择相机分辨率指的是相机传感器捕捉图像细节的能力&#xff0c;具体来说就是传感器上有效像素的总数量。可以把它理解为构成数字图像的“小方块”&#xff08;像素&#xff09;有多少个。工业领域内相机的分辨率的选择根据更具产品需要的精度要求和产品大小来确…

【Web安全】csrf、ssrf和xxe的区别

CSRF、SSRF 和 XXE 是三种不同类型的网络安全漏洞&#xff0c;它们的原理、攻击目标、利用方式和危害场景均有显著区别。以下从核心定义、原理、场景等维度详细对比三者的差异。一、核心定义与原理对比漏洞类型全称核心定义核心原理CSRF跨站请求伪造攻击者诱导用户在已登录的情…

【Lua】XLua一键构建工具

将以下代码放入Editor文件夹&#xff0c;点击菜单栏的XLua/一键生成代码和热补丁 即可。using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine;/// <summary> /// XLua自动化构建工具 //…

20250808:EasyGBS 对接大华 ICC 平台问题处理

最近有个现场在对接大华 ICC 平台时&#xff0c;客户反馈&#xff1a;EasyGBS 级联成功&#xff0c;但 ICC 显示下级离线。EasyGBS 成功对接过很多家国标平台&#xff0c;但这种情况确实少见。我们远程过去确认配置无误后&#xff0c;就进行了抓包&#xff0c;拿到包我就纳闷了…

js使用webscoket时使用自定义二进制包协议时并发问题处理

this.server new WebSocket.Server({ port: this.port });this.server.on(connection, (ws, req) > {const uniqueId dataUtil.uuid();ws.id uniqueId;global.serverSession.set(uniqueId, ws);logger.debug({ message: 客户端已连接, traceId: ws.id, address: req.sock…

元数据管理与数据治理平台:Apache Atlas 分类传播 Classification Propagation

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。Apache Atlas 框架是一套可扩展的核心基础治理服务&#xff0c;使企业能够有效、高效地满足 Hadoop 中的合规性要求&#xff0c;并支持与整个企…

TSF应用开发与运维部署

架构演进历程&#xff1a;单体架构-->SOA架构-->微服务架构-->Service Mesh腾讯微服务平台TSF (Tencent Service Framework) 是一个围绕应用和微服务的 PaaS 平台。提供服务全生命周期管理能力和数据化运营支持。提供多维度应用、服务、机器的监控数据&#xff0c;助力…

linux开发之mmap内存映射

mmap概念 mmp是 将文件或设备直接映射到进程的虚拟内存空间 的一种机制&#xff0c;可实现程序像访问内存一样访问文件&#xff0c;而不需要传统的 read()/write()系统调用 文件内容被映射到进程的地址空间&#xff0c;读写文件就像操作内存一样&#xff0c;操作系统负责自动同…

CPP继承

继承 一、继承概述 1、为什么需要继承 如下示例&#xff0c;Person 类、Student 类、Teacher 类有大量重复的代码&#xff0c;造成代码冗余&#xff0c;降低开发效率。我们可以通过继承来解决这一问题。在面向对象的编程语言中&#xff0c;继承是一个核心概念。主要作用将重复的…

模块 PCB 技术在未来通信领域的创新突破方向

未来通信领域对数据传输速率、信号稳定性及设备集成度的要求持续攀升&#xff0c;模块 PCB 作为通信设备的关键组件&#xff0c;其技术创新成为推动行业发展的核心动力。猎板 PCB 凭借深厚的技术积累与持续的研发投入&#xff0c;在模块 PCB 技术创新方面取得诸多突破&#xff…

mysql的InnoDB索引总结

MySQL InnoDB索引知识点总结 1. 索引类型 1.1 聚簇索引&#xff08;Clustered Index&#xff09; 定义与特性 定义&#xff1a;聚簇索引是InnoDB的默认存储方式&#xff0c;数据行按照主键的顺序物理存储在磁盘上特性&#xff1a; 每个InnoDB表只能有一个聚簇索引数据页中的记录…

C++模板的补充

类模板(上一篇没讲到类模板C/C内存管理&函数模板-CSDN博客&#xff09; 类模板的定义&#xff1a; template<class T1, class T2, ..., class Tn> class 类模板名 {// 类内成员定义 }; 用一个简单的栈例子讲类模板 #define _CRT_SECURE_NO_WARNINGS #include &l…

用JOIN替代子查询的查询性能优化

一、子查询的性能瓶颈分析‌重复执行成本‌关联子查询会导致外层每行数据触发一次子查询&#xff0c;时间复杂度为O(M*N)sql-- 典型低效案例 SELECT e.employee_id, (SELECT d.department_name FROM departments d WHERE d.department_id e.department_id) FROM employees e; …

【设计模式】访问者模式模式

访问者模式&#xff08;Visitor Pattern&#xff09;详解一、访问者模式简介 访问者模式&#xff08;Visitor Pattern&#xff09; 是一种 行为型设计模式&#xff08;对象行为型模式&#xff09;&#xff0c;它允许你在不修改对象结构的前提下&#xff0c;为对象结构中的元素添…