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/PDFusingSystem;usingSystem.Collections.Generic;usingSystem.Text;// 抽象类数据导出器publicabstractclassDataExporter{// 模板方法 - 定义导出流程骨架publicvoidExport(ListDictionarystring,objectdata,stringfilePath){ValidateData(data);varheaderBuildHeader(data);varbodyBuildBody(data);varfooterBuildFooter(data);varcontentAssembleOutput(header,body,footer);WriteToFile(content,filePath);Console.WriteLine($导出完成:{filePath});}protectedvirtualvoidValidateData(ListDictionarystring,objectdata){if(datanull||data.Count0)thrownewArgumentException(数据不能为空);Console.WriteLine($数据验证通过共{data.Count}条记录);}protectedabstractstringBuildHeader(ListDictionarystring,objectdata);protectedabstractstringBuildBody(ListDictionarystring,objectdata);protectedabstractstringBuildFooter(ListDictionarystring,objectdata);protectedabstractstringAssembleOutput(stringheader,stringbody,stringfooter);protectedvirtualvoidWriteToFile(stringcontent,stringfilePath){System.IO.File.WriteAllText(filePath,content);}}// 具体子类CSV 导出器publicclassCsvExporter:DataExporter{protectedoverridestringBuildHeader(ListDictionarystring,objectdata){varkeysdata[0].Keys;returnstring.Join(,,keys);}protectedoverridestringBuildBody(ListDictionarystring,objectdata){varsbnewStringBuilder();foreach(varrowindata){sb.AppendLine(string.Join(,,row.Values));}returnsb.ToString();}protectedoverridestringBuildFooter(ListDictionarystring,objectdata){return$# Total:{data.Count}records;}protectedoverridestringAssembleOutput(stringheader,stringbody,stringfooter){returnheader\nbody\nfooter;}}// 具体子类HTML 导出器publicclassHtmlExporter:DataExporter{protectedoverridestringBuildHeader(ListDictionarystring,objectdata){varsbnewStringBuilder();sb.AppendLine(table border1);sb.AppendLine(tr);foreach(varkeyindata[0].Keys){sb.AppendLine($ th{key}/th);}sb.AppendLine(/tr);returnsb.ToString();}protectedoverridestringBuildBody(ListDictionarystring,objectdata){varsbnewStringBuilder();foreach(varrowindata){sb.AppendLine(tr);foreach(varvalueinrow.Values){sb.AppendLine($ td{value}/td);}sb.AppendLine(/tr);}returnsb.ToString();}protectedoverridestringBuildFooter(ListDictionarystring,objectdata){return$tfoottrtd colspan{data[0].Count}共{data.Count}条/td/tr/tfoot\n/table;}protectedoverridestringAssembleOutput(stringheader,stringbody,stringfooter){return$!DOCTYPE html\nhtml\nbody\n{header}{body}{footer}\n/body\n/html;}}// 使用示例publicclassProgram{publicstaticvoidMain(){vardatanewListDictionarystring,object{new(){[姓名]张三,[年龄]28,[城市]北京},new(){[姓名]李四,[年龄]35,[城市]上海},new(){[姓名]王五,[年龄]42,[城市]深圳},};varcsvExporternewCsvExporter();csvExporter.Export(data,/tmp/export.csv);Console.WriteLine();varhtmlExporternewHtmlExporter();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(){GameInitializergame1newPlatformer2DInitializer();game1.Initialize();GameInitializergame2newShooter3DInitializer();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(){BuildPipelinebuild1newDotNetBuildPipeline();build1.Build();BuildPipelinebuild2newFrontendBuildPipeline();build2.Build();}}✅ 优点提高代码复用将公共部分提取到基类算法骨架固定子类只需关注特定步骤的实现符合开闭原则新增实现只需新增子类钩子机制提供了灵活的扩展点❌ 缺点子类数量可能增多每个不同实现都需要一个子类继承关系使得代码维护困难基类修改影响所有子类调试时需要在父类和子类之间跳转增加理解难度 与其他模式对比模式区别策略模式模板方法用继承改变算法步骤策略模式用组合改变整个算法工厂方法模式工厂方法是模板方法的特例专注于对象创建步骤钩子方法钩子方法是模板方法的一部分提供可选的扩展点