C# 单例模式
在 Unity 开发中,单例模式(Singleton Pattern) 是一种常用的创建型设计模式。它确保一个类在整个应用程序生命周期中只有一个实例,并提供一个全局访问点。这种模式特别适合用于管理游戏状态、全局配置、工具类等需要唯一实例的对象。
C# 单例模式
一、什么是单例模式?
单例模式的核心目标是:
- 确保一个类只有一个实例。
- 提供一个全局访问点,方便其他代码直接调用该实例。
在 Unity 中,单例模式常用于以下场景:
- 管理游戏全局数据(如
GameManager
)。 - 持久化跨场景对象(如音效管理器)。
- 提供工具类或配置管理器。
二、Unity 中单例模式的实现方式
1. 基于 MonoBehaviour
的单例
这是 Unity 中最常见的单例实现方式,适用于需要挂载到 GameObject
上的组件。以下是两种典型实现:
(1)饿汉式(Eager Initialization)
在类加载时立即创建实例,线程安全但可能造成资源浪费。
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
特点:
- 实例在第一次
Awake
时创建。 - 使用
DontDestroyOnLoad
保证跨场景不销毁。 - 避免重复实例化。
(2)懒汉式(Lazy Initialization)
在第一次访问实例时才创建,节省资源但需要注意线程安全。
public class ConfigManager : MonoBehaviour
{
private static ConfigManager _instance;
public static ConfigManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<ConfigManager>();
if (_instance == null)
{
GameObject obj = new GameObject("ConfigManager");
_instance = obj.AddComponent<ConfigManager>();
DontDestroyOnLoad(obj);
}
}
return _instance;
}
}
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
}
}
特点:
- 延迟初始化,减少不必要的内存消耗。
- 通过
FindObjectOfType
检查是否已存在实例。
2. 泛型单例(Generic Singleton)
适用于非 MonoBehaviour
的纯逻辑类,通用性强且易于扩展。
public class Singleton<T> where T : class, new()
{
private static volatile T _instance;
private static readonly object Lock = new object();
public static T Instance
{
get
{
if (_instance == null)
{
lock (Lock)
{
if (_instance == null)
{
_instance = new T();
}
}
}
return _instance;
}
}
protected Singleton() { }
}
// 使用示例
public class DataStorage : Singleton<DataStorage>
{
public int PlayerScore { get; set; }
}
特点:
- 通过泛型实现通用的单例基类。
- 线程安全的双重检查锁(Double-Check Locking)。
3. 结合 Awake
和 DontDestroyOnLoad
的持久化单例
用于确保对象在场景切换时不被销毁,适合全局状态管理。
public class AlwaysPlay : MonoBehaviour
{
private static AlwaysPlay _instance;
public static AlwaysPlay Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<AlwaysPlay>();
if (_instance == null)
{
GameObject obj = new GameObject("AlwaysPlay");
_instance = obj.AddComponent<AlwaysPlay>();
DontDestroyOnLoad(obj);
}
}
return _instance;
}
}
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
}
特点:
- 结合
Awake
和DontDestroyOnLoad
实现跨场景持久化。 - 自动销毁重复实例。
三、单例模式的应用场景
1. 游戏管理器(GameManager)
用于管理游戏状态、关卡进度、全局变量等。
public class GameManager : Singleton<GameManager>
{
public int CurrentLevel { get; set; }
public void LoadNextLevel() { /* 实现加载下一关逻辑 */ }
}
2. 配置管理器(ConfigManager)
加载和存储游戏配置文件。
public class ConfigManager : Singleton<ConfigManager>
{
private Dictionary<string, string> _config = new Dictionary<string, string>();
public void LoadConfig(string path) { /* 加载配置文件 */ }
public string GetConfigValue(string key) { return _config[key]; }
}
3. 工具类(Utility Class)
提供全局可调用的工具方法。
public class MathUtils : Singleton<MathUtils>
{
public int Clamp(int value, int min, int max)
{
return Mathf.Clamp(value, min, max);
}
}
4. 音效管理器(AudioManager)
集中管理音效播放和音量控制。
public class AudioManager : Singleton<AudioManager>
{
public void PlaySound(string soundName) { /* 播放指定音效 */ }
public void SetVolume(float volume) { /* 设置全局音量 */ }
}
四、单例模式的注意事项
1. 避免滥用
- 生命周期不可控:单例实例可能长期占用内存,需手动释放。
- 代码耦合度高:频繁使用单例可能导致模块间依赖复杂,增加维护难度。
2. 线程安全
- 在多线程环境中,需使用
lock
或Lazy<T>
确保线程安全。
3. 内存泄漏风险
- 单例实例无法被垃圾回收(GC),需谨慎管理引用,避免不必要的资源占用。
4. 替代方案
- 事件系统:通过事件中心解耦模块间通信。
- 依赖注入:在大型项目中,使用依赖注入框架(如 Zenject)代替单例。
五、总结
单例模式在 Unity 开发中是一个强大的工具,能够简化全局对象的管理和访问。然而,开发者需要根据具体需求选择合适的实现方式,并注意避免滥用。合理使用单例模式可以提高代码的可维护性和性能,但过度依赖可能导致代码耦合和调试困难。