一句话理解:单例模式保证某个类型在约定范围内只有一个实例,并提供统一访问入口。它适合真正全局且唯一的服务,但不应该成为所有管理器的默认写法。
它解决什么问题
当音频、存档或游戏会话服务需要被多个系统共享时,重复实例会造成状态冲突。单例把“唯一性”和“访问入口”集中管理。
在 Unity 中要先明确实例的生命周期:
- 场景级:切换场景后销毁,不使用
DontDestroyOnLoad
- 应用级:跨场景存在,显式使用
DontDestroyOnLoad
- 纯 C# 服务:不依赖
MonoBehaviour,可由启动流程或依赖注入容器创建
Unity 实现
调用方可以使用
GameSession.Instance.AddScore(10)。如果对象的初始化顺序不确定,应由启动场景负责创建,或在访问前显式检查 Instance,不要依赖脚本执行顺序碰运气。关键细节
DontDestroyOnLoad不是单例模式的一部分,只用于跨场景生命周期。
- Unity API 通常只能在主线程调用;普通
MonoBehaviour单例无需套用桌面应用中的“双重检查锁”。
- 关闭 Domain Reload 时,静态字段可能跨 Play 保留。可以用
RuntimeInitializeOnLoadMethod重置静态状态。
- 泛型基类能减少重复代码,但也会把生命周期策略强加给所有子类,使用前应确认这些服务真的共享同一策略。
什么时候不该用
- 需要多个存档槽、玩家或会话实例
- 单元测试需要轻松替换依赖
- 服务之间依赖关系复杂
- 只是为了避免在 Inspector 中传引用
这些场景更适合构造函数注入、VContainer 等依赖注入方案,或用
ScriptableObject 保存共享配置。优缺点
优点:访问简单、唯一性明确、适合少量应用级服务。
代价:隐藏依赖、全局可变状态、初始化顺序问题,以及测试和复用困难。
小结
先确定生命周期,再决定是否使用单例。真正的判断标准不是“它是不是 Manager”,而是“它是否必须唯一、全局共享,并且值得承担全局状态的成本”。