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();}}✅ 优点
- 提高代码复用,将公共部分提取到基类
- 算法骨架固定,子类只需关注特定步骤的实现
- 符合开闭原则,新增实现只需新增子类
- 钩子机制提供了灵活的扩展点
❌ 缺点
- 子类数量可能增多,每个不同实现都需要一个子类
- 继承关系使得代码维护困难,基类修改影响所有子类
- 调试时需要在父类和子类之间跳转,增加理解难度
📊 与其他模式对比
| 模式 | 区别 |
|---|---|
| 策略模式 | 模板方法用继承改变算法步骤;策略模式用组合改变整个算法 |
| 工厂方法模式 | 工厂方法是模板方法的特例,专注于对象创建步骤 |
| 钩子方法 | 钩子方法是模板方法的一部分,提供可选的扩展点 |