从理论到 C 代码实现)
示例工程【免费下载链接】Unity3DTraining【Unity杂货铺】unity大杂烩~项目地址https://gitcode.com/gh_mirrors/un/Unity3DTraining点击查看免费下载导读本文以 Unity3DTraining 仓库 中DesignPatterns/TemplatePattern模块为核心系统讲解模板方法模式Template Method Pattern的定义、结构、适用场景与局限性并完整解析仓库中配套的可运行 C# 示例抽象模板类示例与手机业务示例。读者学完后将掌握如何在游戏中把固定不变的算法骨架沉淀到抽象基类、把可变步骤延迟到子类实现从而显著提升代码复用率与扩展性。一、模式定义算法骨架与步骤延迟原文档 模板模式说明 给出的定义非常精炼定义一个操作的算法的骨架而将一些步骤延迟到子类中。模板方法可以使得子类不改变一个算法的结构即可重新定义该算法的某些特定步骤。拆解这一定义可以提炼出模板方法模式的两个关键机制算法骨架Algorithm Skeleton固定整体流程、执行顺序由基类中的模板方法Template Method一次性定义好子类无权也无法改动这个顺序步骤延迟Step Delegation到子类骨架中的某些步骤以抽象方法或虚方法的形式暴露由子类提供具体实现从而实现同一流程、不同细节。这种结构本质上体现了面向对象中两大经典原则开闭原则对扩展开放、对修改关闭——新增行为通过新增子类实现而不修改基类骨架依赖倒置原则——高层模块基类依赖于抽象抽象方法而不是依赖于具体实现。仓库中的目录结构也印证了定义 示例的完整教学思路DesignPatterns/TemplatePattern/ ├── README.md # 模式定义、适用场景与局限性 └── TemplatePattern/ # 可运行的 C# 控制台示例工程 ├── AbstractClass.cs # 模板方法的基本代码骨架示例 ├── MobilePhone.cs # 手机示例虚方法 覆盖 ├── NewMobilePhone.cs # 手机示例进阶版protected abstract 钩子 ├── Program.cs # 客户端调用入口 ├── App.config # .NET Framework v4.5.2 运行时配置 ├── TemplatePattern.csproj # 工程文件OutputTypeExe ├── TemplatePattern.sln # 解决方案文件 └── Properties/AssemblyInfo.cs # 程序集元信息二、模式结构抽象模板类与具体子类模板方法模式的角色划分如下角色职责仓库对应实现抽象模板类Abstract Class定义模板方法算法骨架声明一个或多个抽象原语操作AbstractClass.cs具体子类Concrete Class实现抽象原语操作不改变算法执行顺序ConcreteClassA/ConcreteClassB客户端Client通过抽象类型引用具体子类并调用模板方法Program.cs2.1 骨架示例三个原语操作 一个模板方法仓库 AbstractClass.cs 给出了模板方法模式的最小可运行骨架//抽象模板类 abstract class AbstractClass { public abstract void PrimitOpA(); public abstract void PrimitOpB(); public abstract void PrimitOpC(); /// summary /// 模板方法 /// /summary public void TemplateMethod() { PrimitOpA(); PrimitOpB(); PrimitOpC(); } } class ConcreteClassA : AbstractClass { public override void PrimitOpA() { Console.WriteLine(具体类A操作A); } public override void PrimitOpB() { Console.WriteLine(具体类A操作B); } public override void PrimitOpC() { Console.WriteLine(具体类A操作C); } } class ConcreteClassB : AbstractClass { public override void PrimitOpA() { Console.WriteLine(具体类B操作A); } public override void PrimitOpB() { Console.WriteLine(具体类B操作B); } public override void PrimitOpC() { Console.WriteLine(具体类B操作C); } }关键点解读TemplateMethod()是一个非虚的普通方法它内部按固定顺序调用PrimitOpA()→PrimitOpB()→PrimitOpC()这就是不变的算法骨架PrimitOpA/B/C被声明为public abstract是骨架中延迟到子类的可变步骤。C# 中抽象成员没有方法体强制每个具体子类都必须给出实现ConcreteClassA与ConcreteClassB只是各自实现三个原语操作的内容不同它们共同遵守TemplateMethod()中的调用顺序。这正是模板方法模式的价值顺序永远只写一次杜绝了子类各自乱写流程导致的重复代码与不一致。2.2 客户端调用面向抽象编程Program.cs 演示了客户端的标准用法——用抽象基类类型的变量去接具体子类实例AbstractClass abstractClass; abstractClass new ConcreteClassA(); abstractClass.TemplateMethod(); abstractClass new ConcreteClassB(); abstractClass.TemplateMethod();运行时输出依次为具体类A操作A 具体类A操作B 具体类A操作C 具体类B操作A 具体类B操作B 具体类B操作C可以看出客户端完全不知道三个操作的内部实现只面向AbstractClass这一抽象编程这为后续增加ConcreteClassC、ConcreteClassD等新子类留下了扩展点而客户端代码无需任何改动。三、实战示例手机开机-拨号-关机流程如果说上面的骨架示例偏教学那么仓库中的手机示例则更贴近真实业务——它演示了带默认实现的可覆盖步骤virtual与钩子方法protected abstract两种进阶写法。3.1 版本一虚方法 子类覆盖MobilePhone.cs 定义了一个抽象手机基类但四个步骤全部使用virtual并给出默认实现abstract class MobilePhone { public virtual void PowerOn() { Console.WriteLine(手机开机了); } public virtual void PowerOff() { Console.WriteLine(手机关机了); } public virtual void DialUp() { Console.WriteLine(手机拨号); } public virtual void about() { Console.WriteLine(关于手机); } } class MobilePhoneA : MobilePhone { public override void about() { base.about(); Console.WriteLine(A); } } class MobilePhoneB : MobilePhone { public override void about() { base.about(); Console.WriteLine(B); } }这里体现了模板方法模式的一个常见变体基类对部分步骤提供默认实现钩子/默认步骤子类按需覆盖。MobilePhoneA与MobilePhoneB只重写了about()其余开机、关机、拨号行为完全复用基类默认实现——这就是原文档所说将可变的行为留给子类来实现的具体落地。客户端调用来自 Program.csMobilePhoneA mobilePhoneA new MobilePhoneA(); mobilePhoneA.PowerOn(); mobilePhoneA.DialUp(); mobilePhoneA.about(); // 输出: 关于手机 / A mobilePhoneA.PowerOff();3.2 版本二protected abstract 钩子方法个性化注入NewMobilePhone.cs 更进一步引入了一个protected abstract string User()钩子方法——每个步骤的默认输出都拼接User()的返回值abstract class NewMobilePhone { public virtual void PowerOn() { Console.WriteLine(User() 手机开机了); } public virtual void PowerOff() { Console.WriteLine(User() 手机关机了); } public virtual void DialUp() { Console.WriteLine(User() 手机拨号); } public virtual void about() { Console.WriteLine(User() 关于手机); } protected abstract string User(); } class NewMobilePhoneA : NewMobilePhone { protected override string User() { return 大老板; } } class NewMobilePhoneB : NewMobilePhone { protected override string User() { return 秘书; } } class AndroidMobilePhone : NewMobilePhone { protected override string User() { return Android系统手机; } } class WPMobilePhone : NewMobilePhone { protected override string User() { return WP系统手机; } }这个版本的价值在于User()是最小化的扩展点。子类只需回答我是谁基类自动把这句话注入到开机、关机、拨号、关于四个步骤的文案中完全不重复protected访问级别意味着钩子方法只在继承体系内部可见客户端无法直接调用从语言层面保证了步骤与骨架的边界四个子类大老板、秘书、Android、WP的差别被压缩到一行字符串上充分体现子类需要拓展的地方都是固定的仅允许在这些点进行拓展原文档第三点适用情形。原文档 README.md 所指的模版方法模式就是一种反向的调用结构在这个示例中体现得尤为直观子类没有主动去调用基类的任何东西反而是基类的PowerOn()等步骤在运行期反向调用了子类实现的User()。这种好莱坞原则不要打电话给我们我们会打给你式的控制反转正是模板方法模式的精髓。四、适用场景何时该用模板方法模式原文档 模板模式说明 小结部分给出了三条非常实用的判断标准结合仓库代码可以逐一印证一次性地实现一个算法不变的部分而将可变的行为留给子类来实现对应仓库实现AbstractClass.TemplateMethod()一次定义 A→B→C 的固定顺序MobilePhone一次定义开机/关机/拨号的默认文案可变行为如User()留给子类。这类流程固定、细节多变的场景是模板方法模式的最典型入口。当子类中有公共行为可以提取到公共的父类中去并且子类有自己的个性化要求时对应仓库实现四个手机子类共享开机→拨号→关于→关机的完整流程公共行为上提到MobilePhone/NewMobilePhone基类同时每个子类通过覆盖about()或实现User()表达个性化。如果你发现自己正在复制粘贴一段几乎相同的流程代码到多个类里就可以考虑用模板方法模式向上提取。适用于模板方法模式的情形一般子类需要拓展的地方都是固定的即仅允许在这些点进行拓展对应仓库实现骨架中的扩展点被刻意设计为abstract方法或受保护的钩子方法子类只能在既定的槽位上填实现而无法改变整体流程。这种受控扩展让框架代码的演进保持稳定。从源码结构看仓库把该模式与策略模式StrategyPattern、状态模式StatePattern等并列放在 DesignPatterns 总目录 的Unity 中常用的设计模式总结清单中说明该模式被作者视为游戏开发中高频实用的模式之一。五、局限性反向调用结构的代价原文档 模板模式说明 对局限性的论述值得反复体会模板方法模式也是有局限性的事实上模板方法模式就是一种反向的调用结构。抽象类调用了子类的方法而不是传统意义上的子类调用父类方法。正是这种奇特的结构使得拓展和维护的时候更加方便。展开理解这一局限性与补偿价值控制流反转导致阅读跳跃阅读NewMobilePhone.PowerOn()时你看到的只是User() 手机开机了真正执行的内容要到四个子类里去查代码的所见即所得程度下降调试时需要在继承链上跳转继承的固有耦合模板方法模式以继承为实现手段而继承是编译期静态绑定的强耦合关系。如果算法步骤在未来有多种不同的组合方式单一继承的模板方法模式就会显得僵硬——此时可考虑用策略模式将整个算法整体替换或用组合替代继承来获得运行时灵活性基类的稳定性要求更高由于所有子类共享同一骨架骨架一旦变更会影响全部子类因此模板方法尤其强调骨架应当先设计稳定、再开放扩展。当然正如原文档所指出的反向调用结构带来的直接收益是拓展和维护更加方便新增一种手机只需新增一个子类并回答扩展点问题无需触碰基类与客户端这正是模板方法模式长盛不衰的原因。六、在 Unity 游戏开发中的典型应用方向虽然仓库给出的示例是纯 C# 控制台工程TemplatePattern.csproj 中OutputType为Exe目标框架为 .NET Framework v4.5.2但模板方法模式在游戏/Unity 项目中可以推断出如下高频应用方向读者可据此举一反三通用加载/初始化流程把资源加载 → 初始化数据 → 注册事件 → 首次进入表现固化为基类的Init()流程不同界面UI 面板、战斗场景、商店只实现各自的差异化步骤角色/单位行为骨架把感知 → 决策 → 执行 → 冷却作为 AI 行为模板不同兵种通过覆盖决策步骤获得差异化行为关卡/副本流程控制把开局演出 → 波次生成 → 胜利判定 → 结算固定为模板方法具体玩法子类只填充各波次的生成逻辑数据导入/解析管线把读取 → 校验 → 反序列化 → 入库固定为模板不同文件格式只重写解析步骤。需要说明的是以上方向属于由模式特性引申的通用实践仓库内并未提供对应的 Unity 场景工程读者应以本仓库的 C# 示例AbstractClass.cs、MobilePhone.cs、NewMobilePhone.cs为最小可运行范本在 Unity 中把Console.WriteLine替换为 Unity 的 Debug 或业务逻辑即可迁移使用。七、如何运行仓库示例该示例为独立 .NET 控制台工程可直接用 Visual Studio 打开 TemplatePattern.sln 编译运行Debug|AnyCPU配置目标框架 .NET Framework v4.5.2详见 App.config 中的supportedRuntime与工程文件中的TargetFrameworkVersion也可在装有 .NET Framework 4.5.2 及以上环境的命令行中编译# 在 DesignPatterns/TemplatePattern/TemplatePattern 目录下 csc /out:TemplatePattern.exe AbstractClass.cs MobilePhone.cs NewMobilePhone.cs Program.cs ./TemplatePattern.exe运行后将依次看到两个手机示例的输出开机/拨号/关于/关机以及ConcreteClassA、ConcreteClassB两组模板方法调用输出完整覆盖本文第二节与第三节描述的所有行为。八、小结模板方法模式是用继承实现代码复用与控制反转的经典代表本仓库通过 README.md 的定义、适用场景与局限性论述配合 AbstractClass.cs、MobilePhone.cs、NewMobilePhone.cs 与 Program.cs 三层递进的代码示例完整演示了骨架固定、扩展点受控、默认实现可覆盖、钩子方法个性化四个层次的实践技巧。在实际项目中建议按先稳定骨架、再收敛扩展点、最后才允许子类覆盖的顺序推进设计让模板方法模式真正成为提升复用率与可维护性的利器。赞分享示例工程【免费下载链接】Unity3DTraining【Unity杂货铺】unity大杂烩~项目地址https://gitcode.com/gh_mirrors/un/Unity3DTraining点击查看免费下载相关推荐Unity3DTraining 设计模式实战迭代器模式Iterator Pattern原理与 C 实现解析Unity3DTraining 设计模式实战迭代器模式Iterator Pattern原理与 C 实现解析 导读 本文围绕 Unity3DTraining示例工程Unity3DTraining 设计模式系列代理模式Proxy Pattern原理与 C 实战示例Unity3DTraining 设计模式系列代理模式Proxy Pattern原理与 C 实战示例 导读 本文围绕 Unity3DTraining 仓库中示例工程yq 取模运算符%完全指南整数、浮点数与除零行为深度解析yq 取模运算符%完全指南整数、浮点数与除零行为深度解析 导读 本文围绕 yqportable command line YAML/JSON/XML/C示例工程上一篇告别繁琐引导页开发Welcome Coordinator 打造丝滑 Android 欢迎流程下一篇深度学习半监督学习革命TorchSSL完全指南 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考