LOADING

加载过慢请开启缓存 浏览器默认开启

Unity 常用面向对象设计模式系列(1):总览


Unity 常用面向对象设计模式系列(1):总览


引言

在 Unity 引擎中,随着项目规模和复杂度的提升,代码组织方式对可维护性、可扩展性和性能的影响日益显著。设计模式提供了一套行之有效的架构思路,帮助我们在持续迭代中保持代码清晰、职责分明、耦合度低。本系列博客共分八篇,第一篇为总览,介绍 创建型结构型行为型 三大类常用模式,以及它们在 Unity 中的典型应用场景。


一、为何要应用设计模式

  1. 降低耦合度
    通过模式抽象出“变化点”,使得业务逻辑与实现细节分离,任一模块变更只影响单个组件。

  2. 提高内聚性
    每个模式关注单一职责,强化组件专注度,避免“Monster MonoBehaviour”。

  3. 增强可测试性
    接口与依赖注入(DI)配合模式,可用 Mock 替换底层实现,精确到单个行为单元。

  4. 支持扩展而非修改
    新需求往往意味着“新增类型”而非“改动原有逻辑”,设计模式恰好为此提供统一渠道。

  5. 性能与资源管理
    如对象池为高频实例化场景(子弹、特效)显著降低 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 应用GameManagerAudioManagerUIManager 等全局服务。
  • 注意点:慎用静态引用,避免生命周期管理混乱、依赖倒置失效。

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。
  • 注意点:保持固定步骤不可随意修改,子类只填充必要逻辑。

五、何时选用模式

  1. 高频复用 vs 一次性
    • 管理器/服务类多次使用应选 Singleton;
  2. 耦合度 vs 灵活度
    • 想保持松耦合优先组合与策略;
  3. 性能考量
    • 对象池解决 Instantiate/Destroy 瓶颈;
  4. 团队协作
    • 中介者/观察者简化多模块通信;
  5. 扩展需求
    • 新算法多变则用 Strategy;新增类型多用工厂。三、模式选择与组合策略

六、模式选择与组合策略

  • 单例 + 对象池:管理全局对象池的统一入口;
  • 工厂 + 策略:工厂生产带有不同行为策略的实例;
  • 观察者 + 中介者:事件广播与模块协调双管齐下,减少订阅器间耦合;
  • 状态 + 模板方法:在模板骨架中注入不同状态行为,构建流程化系统;
  • 责任链 + 命令:输入分发可通过责任链过滤,再生成可撤销的命令对象。

实践建议

  1. 需求驱动:先评估场景痛点,再选模式,不要“先甩模式再套场景”。
  2. 渐进引入:先在小模块验证效果,避免一次性重构全局。
  3. 结合 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,可轻松替换新实现。