☰
Unity3DTraining 设计模式实战:模板方法模式(Template Method Pattern)从理论到 C 代码实现
2026/9/25 5:07:56 网站建设 项目流程
  • 示例工程

【免费下载链接】Unity3DTraining

【Unity杂货铺】unity大杂烩~

项目地址:https://gitcode.com/gh_mirrors/un/Unity3DTraining
点击查看免费下载

导读

本文以 Unity3DTraining 仓库 中DesignPatterns/TemplatePattern模块为核心,系统讲解模板方法模式(Template Method Pattern)的定义、结构、适用场景与局限性,并完整解析仓库中配套的可运行 C# 示例(抽象模板类示例与"手机"业务示例)。读者学完后,将掌握如何在游戏中把"固定不变的算法骨架"沉淀到抽象基类、把"可变步骤"延迟到子类实现,从而显著提升代码复用率与扩展性。

一、模式定义:算法骨架与步骤延迟

原文档 模板模式说明 给出的定义非常精炼:

定义一个操作的算法的骨架,而将一些步骤延迟到子类中。模板方法可以使得子类不改变一个算法的结构即可重新定义该算法的某些特定步骤。

拆解这一定义,可以提炼出模板方法模式的两个关键机制:

  1. 算法骨架(Algorithm Skeleton)固定:整体流程、执行顺序由基类中的"模板方法"(Template Method)一次性定义好,子类无权也无法改动这个顺序;
  2. 步骤延迟(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 # 工程文件(OutputType=Exe) ├── TemplatePattern.sln # 解决方案文件 └── Properties/AssemblyInfo.cs # 程序集元信息

二、模式结构:抽象模板类与具体子类

模板方法模式的角色划分如下:

角色职责仓库对应实现
抽象模板类(Abstract Class)定义模板方法(算法骨架),声明一个或多个抽象"原语操作"AbstractClass.cs
具体子类(Concrete Class)实现抽象原语操作,不改变算法执行顺序ConcreteClassA/ConcreteClassB
客户端(Client)通过抽象类型引用具体子类并调用模板方法Program.cs

2.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.cs):

MobilePhoneA 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()。这种"好莱坞原则(不要打电话给我们,我们会打给你)"式的控制反转,正是模板方法模式的精髓。

四、适用场景:何时该用模板方法模式

原文档 模板模式说明 小结部分给出了三条非常实用的判断标准,结合仓库代码可以逐一印证:

  1. 一次性地实现一个算法不变的部分,而将可变的行为留给子类来实现

    • 对应仓库实现:AbstractClass.TemplateMethod()一次定义 A→B→C 的固定顺序;MobilePhone一次定义开机/关机/拨号的默认文案,可变行为(如User())留给子类。这类"流程固定、细节多变"的场景是模板方法模式的最典型入口。
  2. 当子类中有公共行为可以提取到公共的父类中去,并且子类有自己的"个性化要求"时

    • 对应仓库实现:四个手机子类共享"开机→拨号→关于→关机"的完整流程,公共行为上提到MobilePhone/NewMobilePhone基类;同时每个子类通过覆盖about()或实现User()表达个性化。如果你发现自己正在复制粘贴一段几乎相同的流程代码到多个类里,就可以考虑用模板方法模式向上提取。
  3. 适用于模板方法模式的情形,一般子类需要拓展的地方都是固定的,即仅允许在这些点进行拓展

    • 对应仓库实现:骨架中的扩展点被刻意设计为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
点击查看免费下载
上一篇:告别繁琐引导页开发:Welcome Coordinator 打造丝滑 Android 欢迎流程
下一篇:深度学习半监督学习革命:TorchSSL完全指南 🚀

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询