Unity 常用面向对象设计模式系列(1):总览
引言
在 Unity 引擎中,随着项目规模和复杂度的提升,代码组织方式对可维护性、可扩展性和性能的影响日益显著。设计模式提供了一套行之有效的架构思路,帮助我们在持续迭代中保持代码清晰、职责分明、耦合度低。本系列博客共分八篇,第一篇为总览,介绍 创建型、结构型、行为型 三大类常用模式,以及它们在 Unity 中的典型应用场景。
一、为何要应用设计模式
降低耦合度
通过模式抽象出“变化点”,使得业务逻辑与实现细节分离,任一模块变更只影响单个组件。提高内聚性
每个模式关注单一职责,强化组件专注度,避免“Monster MonoBehaviour”。增强可测试性
接口与依赖注入(DI)配合模式,可用 Mock 替换底层实现,精确到单个行为单元。支持扩展而非修改
新需求往往意味着“新增类型”而非“改动原有逻辑”,设计模式恰好为此提供统一渠道。性能与资源管理
如对象池为高频实例化场景(子弹、特效)显著降低 GC 与 Instantiate/Destroy 开销;工厂模式配合 ScriptableObject 可将资源预制化,易于资源管理。
设计模式三大分类
| 分类 | 模式 | Unity 常见场景示例 |
|---|---|---|
| 创建型 | - 单例(Singleton) - 工厂(Factory Method / Abstract Factory) - 对象池(Object Pool) |
- 全局管理器 - 动态生成技能、UI、角色 - 弹幕/特效复用 |
| 结构型 | - 适配器(Adapter) - 组合(Composite) |
- 封装第三方 SDK - UI 层级/技能树 |
| 行为型 | - 观察者(Observer/Event) - 命令(Command) - 状态(State) - 策略(Strategy) - 责任链(Chain of Responsibility) - 中介者(Mediator) - 备忘录(Memento) - 模板方法(Template Method) |
- 事件中心、UI 通知 - 输入/撤销 - 角色状态机 - AI 行为切换 - 输入分发 - 模块通信 - 存档/撤销 - 战斗流程骨架 |
二、创建型模式
1. 单例模式(Singleton)
- 核心意图:全局只保留一个实例,提供统一访问入口。
- Unity 应用:
GameManager、AudioManager、UIManager等全局服务。 - 注意点:慎用静态引用,避免生命周期管理混乱、依赖倒置失效。
2. 工厂模式(Factory Method / Abstract Factory)
- 核心意图:将对象创建逻辑封装在工厂类/方法中,客户端只依赖抽象类型。
- Unity 应用:技能系统中按配置动态生成技能对象;UI 面板创建;关卡怪物孵化。
- 注意点:适当用 ScriptableObject 配置驱动,配合依赖注入提升灵活度。
3. 对象池模式(Object Pool)
- 核心意图:维护对象复用队列,减少运行时频繁
Instantiate/Destroy的性能开销。 - Unity 应用:子弹、特效、临时 NPC 等短生命周期对象管理。
- 注意点:池大小预估要合理、重置逻辑需完整清理状态、防止内存泄漏。
三、结构型模式
4. 适配器模式(Adapter)
- 核心意图:将一个类的接口转换为客户端期望的另一个接口,实现兼容性。
- Unity 应用:
- 封装 Android/iOS、本地/云存储 SDK 接口差异;
- 多平台输入(触摸/手柄/键鼠)统一适配。
- 注意点:保持适配器轻量,避免过度包装。
5. 组合模式(Composite)
- 核心意图:将对象组合成树形结构,客户端统一对待“叶子”和“容器”节点。
- Unity 应用:
- UI 元素的父子层级管理;
- 技能树或行为树节点。
- 注意点:树结构变化时,需正确维护父子关系与生命周期。
四、行为型模式
6. 观察者模式(Observer / Event)
- 核心意图:对象之间一对多依赖,当被观察者状态变化时,自动通知所有观察者。
- Unity 应用:
- 全局事件中心(如受伤、关卡完成);
- UI 界面监听模型变更。
- 注意点:注意移除订阅,避免内存泄漏与空引用。
7. 命令模式(Command)
- 核心意图:将请求封装成对象,支持排队、撤销、宏命令等。
- Unity 应用:
- 输入系统:播放/取消技能;
- 撤销系统:编辑器状态回退;
- 自动化脚本:批量操作。
- 注意点:命令对象应持有足够上下文,避免对上下文耦合过深。
8. 状态模式(State)
- 核心意图:将对象的不同行为封装到不同状态类中,通过切换状态对象,改变行为。
- Unity 应用:角色状态机(Idle、Run、Attack、Dead);UI 面板切换状态。
- 注意点:状态切换逻辑需集中维护,防止状态爆炸。
9. 策略模式(Strategy)
- 核心意图:定义一系列可互换的算法或行为,并使其独立于使用它的客户端。
- Unity 应用:AI 行为策略、技能释放策略、路径查找算法切换。
- 注意点:策略接口保持精简,避免策略实现间的隐式依赖。
10. 责任链模式(Chain of Responsibility)
- 核心意图:将请求沿着链传递,直到被某个处理者处理或终止。
- Unity 应用:
- UI 点击/触摸事件拦截;
- 日志 / 网络请求过滤链。
- 注意点:链条长度与顺序直接影响性能与行为,可动态注册/注销节点。
11. 中介者模式(Mediator)
- 核心意图:使用中介对象封装一组对象间的交互,降低直接耦合。
- Unity 应用:
- UI 面板切换协调;
- 子系统间消息总线。
- 注意点:中介者本身可能成为“巨大对象”,需合理拆分子中介。
12. 备忘录模式(Memento)
- 核心意图:在不破坏封装的前提下,捕获并恢复对象内部状态。
- Unity 应用:
- 存档/读档;
- 编辑模式下的撤销/重做。
- 注意点:状态快照可能很大,需考虑增量存储与序列化性能。
13. 模板方法模式(Template Method)
- 核心意图:在父类中定义算法骨架,而将部分步骤延迟到子类实现。
- Unity 应用:
- 战斗流程(准备→执行→结算)模板;
- MonoBehaviour 生命周期自定义 Hook。
- 注意点:保持固定步骤不可随意修改,子类只填充必要逻辑。
五、何时选用模式
- 高频复用 vs 一次性
- 管理器/服务类多次使用应选 Singleton;
- 耦合度 vs 灵活度
- 想保持松耦合优先组合与策略;
- 性能考量
- 对象池解决 Instantiate/Destroy 瓶颈;
- 团队协作
- 中介者/观察者简化多模块通信;
- 扩展需求
- 新算法多变则用 Strategy;新增类型多用工厂。三、模式选择与组合策略
六、模式选择与组合策略
- 单例 + 对象池:管理全局对象池的统一入口;
- 工厂 + 策略:工厂生产带有不同行为策略的实例;
- 观察者 + 中介者:事件广播与模块协调双管齐下,减少订阅器间耦合;
- 状态 + 模板方法:在模板骨架中注入不同状态行为,构建流程化系统;
- 责任链 + 命令:输入分发可通过责任链过滤,再生成可撤销的命令对象。
实践建议:
- 需求驱动:先评估场景痛点,再选模式,不要“先甩模式再套场景”。
- 渐进引入:先在小模块验证效果,避免一次性重构全局。
- 结合 Unity 特性:利用 ScriptableObject、UnityEvent、Coroutines 等,贴合引擎设计而非机械套用。
七、快速示例:工厂 + ScriptableObject
为了说明如何让工厂模式与 Unity 配合,下面给出片段示例,演示“以资产驱动的工厂”。
// 1. 定义产品接口
public interface IProjectile {
void Launch(Vector3 dir);
}
// 2. ScriptableObject 工厂配置
[CreateAssetMenu("Factory/ProjectileFactory")]
public class ProjectileFactory : ScriptableObject {
[SerializeField] GameObject[] projectilePrefabs;
// 按类型索引生产
public IProjectile Create(int index) {
var go = Instantiate(projectilePrefabs[index]);
return go.GetComponent<IProjectile>();
}
}
// 3. 客户端使用
public class PlayerShooter : MonoBehaviour {
public ProjectileFactory factory;
void Update() {
if (Input.GetButtonDown("Fire1")) {
var proj = factory.Create(0);
proj.Launch(transform.forward);
}
}
}
工厂与资源解耦:Premab 制作后可随时替换,无需改动代码;
接口抽象:客户端仅依赖 IProjectile,可轻松替换新实现。