news 2026/6/10 4:06:24

24. 模板方法模式(Template Method Pattern)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
24. 模板方法模式(Template Method Pattern)

24. 模板方法模式(Template Method Pattern)

分类: 行为型模式
热门度: ★★★★☆
难度: ★★☆☆☆


📖 概念

模板方法模式在基类中定义一个算法的骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。它是基于继承的行为扩展模式。

🎯 意图

定义一个操作中算法的骨架,将某些步骤的实现延迟到子类,使得子类可以不改变算法结构即可重定义算法的某些步骤。

🔑 关键角色

角色说明
AbstractClass(抽象类)定义模板方法(算法骨架)和抽象/钩子方法
TemplateMethod(模板方法)定义算法骨架,调用一系列抽象方法和钩子方法
AbstractMethod(抽象方法)声明但不实现,强制子类提供实现
HookMethod(钩子方法)提供默认实现(通常为空),子类可选择性覆盖
ConcreteClass(具体子类)实现抽象方法,可选择覆盖钩子方法

⚠️ 注意事项

  • 模板方法应声明为sealed或不标记virtual,防止子类覆盖算法骨架
  • 抽象方法不宜过多,否则子类实现负担过重
  • 钩子方法应提供合理的默认实现
  • 避免"调用别处"反模式:模板方法调用的方法不应是子类不知道的

🔄 实现流程

1. 抽象类定义模板方法(算法骨架),通常标记为 sealed 2. 模板方法按顺序调用若干步骤方法 3. 将必须由子类实现的步骤定义为抽象方法 4. 将可选的步骤定义为钩子方法(提供默认实现) 5. 具体子类继承抽象类,实现抽象方法 6. 具体子类根据需要覆盖钩子方法 7. 客户端调用模板方法,算法骨架不变,但具体行为由子类决定

💡 常见使用场景

场景1: 数据导出(CSV/Excel/PDF)

usingSystem;usingSystem.Collections.Generic;usingSystem.Text;// 抽象类:数据导出器publicabstractclassDataExporter{// 模板方法 - 定义导出流程骨架publicvoidExport(List<Dictionary<string,object>>data,stringfilePath){ValidateData(data);varheader=BuildHeader(data);varbody=BuildBody(data);varfooter=BuildFooter(data);varcontent=AssembleOutput(header,body,footer);WriteToFile(content,filePath);Console.WriteLine($"导出完成:{filePath}");}protectedvirtualvoidValidateData(List<Dictionary<string,object>>data){if(data==null||data.Count==0)thrownewArgumentException("数据不能为空");Console.WriteLine($"数据验证通过,共{data.Count}条记录");}protectedabstractstringBuildHeader(List<Dictionary<string,object>>data);protectedabstractstringBuildBody(List<Dictionary<string,object>>data);protectedabstractstringBuildFooter(List<Dictionary<string,object>>data);protectedabstractstringAssembleOutput(stringheader,stringbody,stringfooter);protectedvirtualvoidWriteToFile(stringcontent,stringfilePath){System.IO.File.WriteAllText(filePath,content);}}// 具体子类:CSV 导出器publicclassCsvExporter:DataExporter{protectedoverridestringBuildHeader(List<Dictionary<string,object>>data){varkeys=data[0].Keys;returnstring.Join(",",keys);}protectedoverridestringBuildBody(List<Dictionary<string,object>>data){varsb=newStringBuilder();foreach(varrowindata){sb.AppendLine(string.Join(",",row.Values));}returnsb.ToString();}protectedoverridestringBuildFooter(List<Dictionary<string,object>>data){return$"# Total:{data.Count}records";}protectedoverridestringAssembleOutput(stringheader,stringbody,stringfooter){returnheader+"\n"+body+"\n"+footer;}}// 具体子类:HTML 导出器publicclassHtmlExporter:DataExporter{protectedoverridestringBuildHeader(List<Dictionary<string,object>>data){varsb=newStringBuilder();sb.AppendLine("<table border='1'>");sb.AppendLine("<tr>");foreach(varkeyindata[0].Keys){sb.AppendLine($" <th>{key}</th>");}sb.AppendLine("</tr>");returnsb.ToString();}protectedoverridestringBuildBody(List<Dictionary<string,object>>data){varsb=newStringBuilder();foreach(varrowindata){sb.AppendLine("<tr>");foreach(varvalueinrow.Values){sb.AppendLine($" <td>{value}</td>");}sb.AppendLine("</tr>");}returnsb.ToString();}protectedoverridestringBuildFooter(List<Dictionary<string,object>>data){return$"<tfoot><tr><td colspan='{data[0].Count}'>共{data.Count}条</td></tr></tfoot>\n</table>";}protectedoverridestringAssembleOutput(stringheader,stringbody,stringfooter){return$"<!DOCTYPE html>\n<html>\n<body>\n{header}{body}{footer}\n</body>\n</html>";}}// 使用示例publicclassProgram{publicstaticvoidMain(){vardata=newList<Dictionary<string,object>>{new(){["姓名"]="张三",["年龄"]=28,["城市"]="北京"},new(){["姓名"]="李四",["年龄"]=35,["城市"]="上海"},new(){["姓名"]="王五",["年龄"]=42,["城市"]="深圳"},};varcsvExporter=newCsvExporter();csvExporter.Export(data,"/tmp/export.csv");Console.WriteLine();varhtmlExporter=newHtmlExporter();htmlExporter.Export(data,"/tmp/export.html");}}

场景2: 游戏初始化流程

usingSystem;// 抽象类:游戏初始化器publicabstractclassGameInitializer{// 模板方法publicvoidInitialize(){Console.WriteLine("===== 游戏初始化 =====");LoadConfig();InitGraphics();LoadAssets();SetupAudio();InitPhysics();if(ShouldShowIntro()){ShowIntro();}StartGameLoop();Console.WriteLine("===== 初始化完成 =====\n");}protectedabstractvoidLoadConfig();protectedabstractvoidInitGraphics();protectedabstractvoidLoadAssets();// 钩子方法 - 有默认实现protectedvirtualvoidSetupAudio(){Console.WriteLine("[音频] 使用默认音频设置");}protectedvirtualvoidInitPhysics(){Console.WriteLine("[物理] 使用默认物理引擎");}protectedvirtualboolShouldShowIntro()=>true;protectedvirtualvoidShowIntro(){Console.WriteLine("[开场] 播放默认开场动画");}protectedvirtualvoidStartGameLoop(){Console.WriteLine("[主循环] 启动游戏主循环");}}// 具体子类:2D 平台跳跃游戏publicclassPlatformer2DInitializer:GameInitializer{protectedoverridevoidLoadConfig(){Console.WriteLine("[配置] 加载 2D 平台游戏配置");}protectedoverridevoidInitGraphics(){Console.WriteLine("[图形] 初始化 2D 精素渲染引擎");}protectedoverridevoidLoadAssets(){Console.WriteLine("[资源] 加载精灵图、瓦片地图");}protectedoverridevoidSetupAudio(){Console.WriteLine("[音频] 加载 chiptune 音效");}protectedoverrideboolShouldShowIntro()=>false;// 2D 游戏跳过开场}// 具体子类:3D 射击游戏publicclassShooter3DInitializer:GameInitializer{protectedoverridevoidLoadConfig(){Console.WriteLine("[配置] 加载 3D 射击游戏配置 (分辨率: 4K)");}protectedoverridevoidInitGraphics(){Console.WriteLine("[图形] 初始化 Vulkan 渲染管线");}protectedoverridevoidLoadAssets(){Console.WriteLine("[资源] 加载 3D 模型、材质、着色器");}protectedoverridevoidInitPhysics(){Console.WriteLine("[物理] 初始化 Bullet 物理引擎");}protectedoverridevoidShowIntro(){Console.WriteLine("[开场] 播放 CG 开场动画");}}// 使用示例publicclassProgram{publicstaticvoidMain(){GameInitializergame1=newPlatformer2DInitializer();game1.Initialize();GameInitializergame2=newShooter3DInitializer();game2.Initialize();}}

场景3: 构建流程(编译/测试/打包)

usingSystem;// 抽象类:构建管道publicabstractclassBuildPipeline{// 模板方法publicvoidBuild(){Console.WriteLine($"=== 开始构建:{GetProjectName()}===");Clean();Restore();Compile();if(ShouldRunTests()){RunTests();}Package();if(ShouldDeploy()){Deploy();}Console.WriteLine($"=== 构建完成 ===\n");}protectedabstractstringGetProjectName();protectedabstractvoidClean();protectedabstractvoidRestore();protectedabstractvoidCompile();protectedabstractvoidRunTests();protectedabstractvoidPackage();protectedvirtualboolShouldRunTests()=>true;protectedvirtualboolShouldDeploy()=>false;protectedvirtualvoidDeploy(){Console.WriteLine("[部署] 默认部署到本地");}}// 具体子类:.NET 项目构建publicclassDotNetBuildPipeline:BuildPipeline{protectedoverridestringGetProjectName()=>"MyWebApp (.NET 8)";protectedoverridevoidClean(){Console.WriteLine("[清理] dotnet clean --configuration Release");}protectedoverridevoidRestore(){Console.WriteLine("[还原] dotnet restore");}protectedoverridevoidCompile(){Console.WriteLine("[编译] dotnet build --configuration Release");}protectedoverridevoidRunTests(){Console.WriteLine("[测试] dotnet test --no-build");}protectedoverridevoidPackage(){Console.WriteLine("[打包] dotnet publish -c Release -o ./publish");}protectedoverrideboolShouldDeploy()=>true;protectedoverridevoidDeploy(){Console.WriteLine("[部署] 部署到 Azure App Service");}}// 具体子类:前端项目构建publicclassFrontendBuildPipeline:BuildPipeline{protectedoverridestringGetProjectName()=>"ReactApp (Node.js)";protectedoverridevoidClean(){Console.WriteLine("[清理] rm -rf dist/ node_modules/.cache/");}protectedoverridevoidRestore(){Console.WriteLine("[还原] npm ci");}protectedoverridevoidCompile(){Console.WriteLine("[编译] npm run build");}protectedoverridevoidRunTests(){Console.WriteLine("[测试] npm run test -- --coverage");}protectedoverridevoidPackage(){Console.WriteLine("[打包] tar -czf dist.tar.gz dist/");}protectedoverrideboolShouldDeploy()=>true;protectedoverridevoidDeploy(){Console.WriteLine("[部署] 部署到 CDN (CloudFront)");}}// 使用示例publicclassProgram{publicstaticvoidMain(){BuildPipelinebuild1=newDotNetBuildPipeline();build1.Build();BuildPipelinebuild2=newFrontendBuildPipeline();build2.Build();}}

✅ 优点

  • 提高代码复用,将公共部分提取到基类
  • 算法骨架固定,子类只需关注特定步骤的实现
  • 符合开闭原则,新增实现只需新增子类
  • 钩子机制提供了灵活的扩展点

❌ 缺点

  • 子类数量可能增多,每个不同实现都需要一个子类
  • 继承关系使得代码维护困难,基类修改影响所有子类
  • 调试时需要在父类和子类之间跳转,增加理解难度

📊 与其他模式对比

模式区别
策略模式模板方法用继承改变算法步骤;策略模式用组合改变整个算法
工厂方法模式工厂方法是模板方法的特例,专注于对象创建步骤
钩子方法钩子方法是模板方法的一部分,提供可选的扩展点
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 3:56:17

基于 Simulink 的新能源商用车主驱电机弱磁扩速控制策略仿真实战教程

目录 一、 核心原理:突破电压极限的“降维打击” 二、 Simulink 模型架构与搭建 第一步:搭建商用车 FOC 动力骨架 第二步:构建弱磁电压监控与 PI 补偿(核心) 三、 仿真场景设置与结果解读 四、 进阶:效率优化与自适应弱磁 五、 避坑指南与工程设计建议 这是一份基…

作者头像 李华
网站建设 2026/6/10 3:53:21

【花雕学编程】Arduino BLDC 之地震废墟搜索机器人

基于Arduino BLDC的地震废墟搜索机器人是一种针对灾后复杂环境的智能化解决方案&#xff0c;以下从专业视角详细解析其主要特点、应用场景及关键注意事项&#xff1a; 一、主要特点 强环境适应性与高机动性 全地形移动平台&#xff1a;采用履带式或足轮混合式底盘&#xff0c;…

作者头像 李华
网站建设 2026/6/10 3:46:28

Python 描述符专项练习:6 道编程题从入门到精通

配套专栏:Python 全栈修炼之路 第 15 篇《描述符与属性访问控制》 难度分布:⭐ → ⭐⭐ → ⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐ → ⭐⭐⭐⭐ 核心覆盖:__get__/__set__/__delete__、数据/非数据描述符、属性查找优先级、property 本质、弱引用存储、ORM 实战 前言 描述符是 Python 属…

作者头像 李华
网站建设 2026/6/10 3:46:18

Exercise003_Even_or_Odd

Exercise 3: Even or Odd 题目重现 原题标题&#xff1a;Even or Odd 原题描述&#xff1a;Write a program that asks the user to enter a number and then determines whether the number is even or odd. 中文说明&#xff1a;编写一个程序&#xff0c;要求用户输入一个数字…

作者头像 李华
网站建设 2026/6/10 3:42:50

从一次CANoe测试失败案例,聊聊CAPL变量作用域那些容易忽略的细节

从一次CANoe测试失败案例&#xff0c;聊聊CAPL变量作用域那些容易忽略的细节那天下午三点&#xff0c;实验室的空调嗡嗡作响&#xff0c;我盯着屏幕上CANoe测试报告中那个诡异的"0xFE"错误码&#xff0c;咖啡已经凉了第三杯。作为负责整车ECU网络通信测试的工程师&am…

作者头像 李华