当前位置: 首页 > news >正文

新谈设计模式 Chapter 21 — 模板方法模式 Template Method

Chapter 21 — 模板方法模式 Template Method

灵魂速记:考试卷子——题目框架一样,答案各写各的。


秒懂类比

期末考试:

  • 卷子模板:第一题填空、第二题选择、第三题论述
  • 学生A:按自己的理解填答案
  • 学生B:按自己的理解填答案

试卷框架(算法骨架)不变,具体答案(某些步骤的实现)由子类决定。


问题引入

// 灾难现场:两个类的代码 90% 一样classCSVDataMiner{voidmine(){openFile();// 相同extractCSVData();// 不同!parseData();// 相同analyzeData();// 相同generateCSVReport();// 不同!closeFile();// 相同}};classJSONDataMiner{voidmine(){openFile();// 相同extractJSONData();// 不同!parseData();// 相同analyzeData();// 相同generateJSONReport();// 不同!closeFile();// 相同}};// 6 个步骤中 4 个完全重复!// 加一种格式(XML)?再复制一遍?

模式结构

┌───────────────────────┐ │ AbstractClass │ ├───────────────────────┤ │ +templateMethod() { │ ← 模板方法:定义算法骨架(final) │ step1(); │ 固定步骤 │ step2(); │ ← 抽象,子类实现 │ step3(); │ 固定步骤 │ hook(); │ ← 钩子,子类可选重写 │ step4(); │ ← 抽象,子类实现 │ } │ │ +step1() 具体实现 │ │ +step2() 纯虚函数 │ │ +step4() 纯虚函数 │ │ +hook() 默认空实现 │ └───────────┬───────────┘ │ 继承 ┌───────┴───────┐ │ │ ┌───┴───┐ ┌─────┴──┐ │ClassA │ │ClassB │ │+step2│ │+step2 │ │+step4│ │+step4 │ └───────┘ └────────┘

C++ 实现

#include<iostream>#include<string>#include<vector>// ========== 抽象类:数据挖掘模板 ==========classDataMiner{public:virtual~DataMiner()=default;// 模板方法:定义算法骨架,不应被子类重写。// 加 virtual + final 可以彻底防止子类通过多态重写:// virtual void mine(const std::string& path) final { ... }// 注意:final 只能用于 virtual 函数,非 virtual 函数加 final 是编译错误。// 这里为了简洁保持非虚:非虚函数无法被多态重写,只能被子类「隐藏」(hiding),// 大多数场景已经够用了。voidmine(conststd::string&path){std::cout<<"━━━ 开始数据挖掘: "<<path<<" ━━━\n";openFile(path);// 固定步骤autorawData=extractData();// 子类实现autodata=parseData(rawData);// 固定步骤analyzeData(data);// 固定步骤if(shouldGenerateReport()){// 钩子!generateReport(data);// 子类实现}closeFile();// 固定步骤std::cout<<"━━━ 挖掘完成 ━━━\n\n";}protected:// 固定步骤:基类实现voidopenFile(conststd::string&path){std::cout<<" 📂 打开文件: "<<path<<"\n";}voidcloseFile(){std::cout<<" 📁 关闭文件\n";}std::vector<int>parseData(conststd::string&rawData){std::cout<<" 🔄 解析数据: "<<rawData.size()<<" 字节\n";return{1,2,3,4,5};// 简化}voidanalyzeData(conststd::vector<int>&data){intsum=0;for(intv:data)sum+=v;std::cout<<" 📊 分析数据: "<<data.size()<<" 条记录, 总和="<<sum<<"\n";}// 抽象步骤:子类必须实现virtualstd::stringextractData()=0;virtualvoidgenerateReport(conststd::vector<int>&data)=0;// 钩子:子类可选重写(默认返回 true)virtualboolshouldGenerateReport(){returntrue;}};// ========== 具体类:CSV 矿工 ==========classCSVDataMiner:publicDataMiner{protected:std::stringextractData()override{std::cout<<" 📄 提取 CSV 数据(逗号分隔)\n";return"1,2,3,4,5";}voidgenerateReport(conststd::vector<int>&data)override{std::cout<<" 📝 生成 CSV 报告: data.csv\n";}};// ========== 具体类:JSON 矿工 ==========classJSONDataMiner:publicDataMiner{protected:std::stringextractData()override{std::cout<<" 📄 提取 JSON 数据(键值对)\n";returnR"({"data":[1,2,3,4,5]})";}voidgenerateReport(conststd::vector<int>&data)override{std::cout<<" 📝 生成 JSON 报告: report.json\n";}};// ========== 具体类:PDF 矿工(不生成报告) ==========classPDFDataMiner:publicDataMiner{protected:std::stringextractData()override{std::cout<<" 📄 提取 PDF 数据(OCR 识别)\n";return"OCR extracted text";}voidgenerateReport(conststd::vector<int>&)override{// 不会被调用,因为钩子返回 false}// 重写钩子:PDF 不生成报告boolshouldGenerateReport()override{returnfalse;}};intmain(){CSVDataMiner csv;csv.mine("sales.csv");JSONDataMiner json;json.mine("users.json");PDFDataMiner pdf;pdf.mine("contract.pdf");}

输出:

━━━ 开始数据挖掘: sales.csv ━━━ 📂 打开文件: sales.csv 📄 提取 CSV 数据(逗号分隔) 🔄 解析数据: 9 字节 📊 分析数据: 5 条记录, 总和=15 📝 生成 CSV 报告: data.csv 📁 关闭文件 ━━━ 挖掘完成 ━━━ ━━━ 开始数据挖掘: users.json ━━━ 📂 打开文件: users.json 📄 提取 JSON 数据(键值对) 🔄 解析数据: 22 字节 📊 分析数据: 5 条记录, 总和=15 📝 生成 JSON 报告: report.json 📁 关闭文件 ━━━ 挖掘完成 ━━━ ━━━ 开始数据挖掘: contract.pdf ━━━ 📂 打开文件: contract.pdf 📄 提取 PDF 数据(OCR 识别) 🔄 解析数据: 18 字节 📊 分析数据: 5 条记录, 总和=15 📁 关闭文件 ━━━ 挖掘完成 ━━━

钩子(Hook)

钩子是模板方法中的可选扩展点

classGame{public:voidplay(){initialize();while(!isGameOver()){// ← 这就是钩子takeTurn();}displayResults();}protected:virtualvoidinitialize()=0;virtualvoidtakeTurn()=0;virtualvoiddisplayResults()=0;// 钩子:默认实现,子类可以重写virtualboolisGameOver(){returnturnCount_++>10;}intturnCount_=0;};

什么时候用?

✅ 适合❌ 别用
多个类有相似的算法框架每个子类的流程完全不同
想固定算法骨架,让子类改具体步骤算法需要运行时整体替换(用策略)
消除子类中的重复代码只有一个子类,没必要

防混淆

Template Method vs Strategy

Template MethodStrategy
机制继承组合
替换什么算法的某几个步骤整个算法
决定时机编译时(子类继承)运行时(设置策略对象)
灵活度低(改继承要改代码)高(运行时换)
类比考试卷子(框架固定)导航路线(完全换方案)

一句话分清:Template Method 换零件,Strategy 换整台机器。

Template Method vs Factory Method

Factory Method 其实是 Template Method 的一种特殊应用——模板方法中的某个"步骤"恰好是"创建对象"。


好莱坞原则

模板方法遵循好莱坞原则

“Don’t call us, we’ll call you.”(别找我,我会找你。)

子类不调用父类的方法,而是父类的模板方法在需要的时候调用子类的实现。控制权在父类手里。


现代 C++ 小贴士:CRTP 静态模板方法

经典版本的模板方法用的是运行时虚函数。如果算法骨架在编译期就能确定(不需要运行时多态),可以用CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)实现零开销的静态版本:

template<typenameDerived>classDataMinerBase{public:// 模板方法:调用派生类的具体步骤voidmine(conststd::string&path){openFile(path);autoraw=static_cast<Derived*>(this)->extractData();// 静态分派autodata=parseData(raw);analyzeData(data);static_cast<Derived*>(this)->generateReport(data);// 静态分派closeFile();}protected:voidopenFile(conststd::string&path){/* 通用实现 */}voidcloseFile(){/* 通用实现 */}std::vector<int>parseData(conststd::string&){return{1,2,3};}voidanalyzeData(conststd::vector<int>&){/* 通用实现 */}};classCSVMiner:publicDataMinerBase<CSVMiner>{public:std::stringextractData(){return"csv data";}voidgenerateReport(conststd::vector<int>&){/* CSV 报告 */}};

CRTP 的static_cast<Derived*>(this)->method()在编译期解析,没有虚函数调用开销。
代价是:失去运行时多态(不能用基类指针统一操作不同类型的 Miner)。

选择法则

  • 需要运行时多态 → 经典虚函数版本
  • 编译期确定类型、追求零开销 → CRTP 版本
http://www.jsqmd.com/news/714441/

相关文章:

  • 2026年郑州冷库工程与家电维修一站式服务商深度横评:本地化响应如何打破行业分散困局 - 优质企业观察收录
  • VS Code Dev Containers性能对比评测报告(2024真实基准测试数据曝光)
  • 3大核心模块深度解析:AI物理计算框架实战指南
  • 国产替代SYPS-2-33+
  • 简单理解:Nyquist(奈奎斯特)架构
  • 告别光电编码器?聊聊MT6835磁编码器在伺服电机控制中的实战应用与选型心得
  • 南京乐意工程机械租赁:南京货物装卸公司推荐 - LYL仔仔
  • Java 25结构化并发落地清单(含Checklist.xlsx+ByteBuddy增强插件+Prometheus监控埋点模板),仅限首批200家ISV申请下载
  • 2026最新中高端牛仔面料生产厂家推荐!国内优质权威榜单发布,广东佛山等地高性价比厂家精选 - 十大品牌榜
  • 3分钟搞定B站字幕下载:告别手动抄录,高效获取视频字幕资源
  • Qwen3-ASR-1.7B实战教程:与Qwen3-ForcedAligner-0.6B联用方案
  • 别再乱调参数了!用Python和OpenCV搞懂高斯模糊的sigma和radius到底怎么配
  • 如何高价回收瑞祥商联卡?最安全的线上平台推荐 - 团团收购物卡回收
  • 计算机组成原理知识问答系统:基于LiuJuan20260223Zimage的实现
  • 代码规范检查工具
  • 2026最新弹力牛仔厂家推荐!国内优质权威榜单发布,广东佛山等地靠谱厂家值得选择 - 十大品牌榜
  • 分布式、集群、同步、异步
  • 终极Win11Debloat系统优化指南:如何通过PowerShell脚本快速清理Windows臃肿应用
  • QQ空间历史说说备份终极指南:如何一键保存你的青春记忆
  • Oumuamua-7b-RP进阶技巧:利用‘背景’字段注入世界观设定提升剧情连贯性
  • 终极Windows 11精简优化指南:Win11Debloat让你的系统焕然一新
  • 新鲜出炉!2026巴西本土公司注册的中国服务商推荐排行 专业评测榜 合规高效/全链条服务​ - 极欧测评
  • 收藏|2026年版AI入行全攻略!不同背景零基础小白程序员大模型转行避坑指南
  • 权威榜单!2026巴西本土公司注册的中国服务商推荐排行 本地化服务/全程无忧​ - 极欧测评
  • 量子比特态矢量模拟的内存爆炸难题,如何用RAII+SIMD+稀疏张量压缩将内存占用降低92%?
  • CMSIS-RTOSv2兼容性突然中断?:2026规范强制迁移至POSIX-RT子集的4步平滑过渡方案(含GCC13.4补丁包)
  • DepotDownloader:专业级Steam内容下载与版本管理实战指南
  • 夏天刚需清爽防晒黑防晒霜,Leeyo防晒霜水润轻薄全方位防光老化 - 全网最美
  • 2026最新牛仔布料供应商推荐!国内优质权威榜单发布,广东佛山等地高性价比供应商精选 - 十大品牌榜
  • Unity动态图像终极解决方案:UniGif GIF解码器深度解析与实战指南