Home
avatar

lnblxj

C# 单例模式

在 Unity 开发中,单例模式(Singleton Pattern) 是一种常用的创建型设计模式。它确保一个类在整个应用程序生命周期中只有一个实例,并提供一个全局访问点。这种模式特别适合用于管理游戏状态、全局配置、工具类等需要唯一实例的对象。

C# 单例模式


一、什么是单例模式?

单例模式的核心目标是:

  1. 确保一个类只有一个实例
  2. 提供一个全局访问点,方便其他代码直接调用该实例。

在 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. 结合 AwakeDontDestroyOnLoad 的持久化单例

用于确保对象在场景切换时不被销毁,适合全局状态管理。

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);
    }
}

特点

  • 结合 AwakeDontDestroyOnLoad 实现跨场景持久化。
  • 自动销毁重复实例。

三、单例模式的应用场景

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. 线程安全

  • 在多线程环境中,需使用 lockLazy<T> 确保线程安全。

3. 内存泄漏风险

  • 单例实例无法被垃圾回收(GC),需谨慎管理引用,避免不必要的资源占用。

4. 替代方案

  • 事件系统:通过事件中心解耦模块间通信。
  • 依赖注入:在大型项目中,使用依赖注入框架(如 Zenject)代替单例。

五、总结

单例模式在 Unity 开发中是一个强大的工具,能够简化全局对象的管理和访问。然而,开发者需要根据具体需求选择合适的实现方式,并注意避免滥用。合理使用单例模式可以提高代码的可维护性和性能,但过度依赖可能导致代码耦合和调试困难。


参考资料


C# Unity