# 单例模式
属于创建型模式,是常用的设计模式之一。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 适用性:场景中存在唯一的对象,需要频繁的使用
CSharp 类与 Unity 脚本之间是有差别的,接下来将分开介绍
# CSharp 类
# 懒汉式,线程不安全
- 这种方式是最基本的实现方式
- 按需加载 / 懒初始化:只有在调用时才会初始化
- 缺点:多线程同时访问时,可能会创建多个对象
| public class Singleton |
| { |
| |
| |
| |
| private Singleton() { } |
| |
| |
| |
| |
| private static Singleton? instance; |
| |
| |
| |
| public static Singleton Instance |
| { |
| get |
| { |
| if (instance == null) |
| { |
| instance = new Singleton(); |
| } |
| return instance; |
| } |
| } |
| } |
# 懒汉式,线程安全,双检锁 / 双重校验锁(double-checked locking)
| public class Singleton |
| { |
| private Singleton() { } |
| |
| private static object Locker = new object(); |
| private static Singleton? instance; |
| public static Singleton Instance |
| { |
| get |
| { |
| |
| if (instance == null) |
| { |
| |
| lock (Locker) |
| { |
| |
| if (instance == null) |
| { |
| instance = new Singleton(); |
| } |
| } |
| } |
| |
| return instance; |
| } |
| } |
| } |
# 饿汉式
- 一般情况下,建议使用饿汉式
- 早初始化:在类被加载的时候就初始化
- 过早初始化会浪费内存
- 多线程安全
| public class Singleton |
| { |
| private Singleton() { } |
| |
| private static Singleton instance = new Singleton(); |
| public static Singleton Instance |
| { |
| get |
| { |
| return instance; |
| } |
| } |
| } |
# 泛型抽象类,以饿汉式为例
- 将单例类的共同的部分封装到父类
- 父类利用反射创建子类
| public abstract class Singleton_T<T> where T : Singleton_T<T> |
| { |
| protected Singleton_T() { } |
| |
| private static T instance = Activator.CreateInstance(typeof(T)) as T; |
| public static T Instance |
| { |
| get |
| { |
| return instance; |
| } |
| } |
| } |
# Unity 脚本
# 分析
- 脚本挂载到物体上就会生成一个对象,所以不要把脚本挂载到多个物体上。如果有必要,可以做生成数量的检测(本文没有做这样的检测)
- 为保证单例模式的通用性,使用泛型类,让子类继承
- 声明静态字段和静态属性,给外界提供属性。
- 按需加载(懒汉式),根据传入的子类类型,返回对应的对象,如果对象不存在,则自行创建一个。
- 如果脚本已经挂载到物体上,就可以直接在 Awake () 中对静态字段赋值
- 如果脚本没有挂载到物体上,需要新创建一个物体,创建物体会立即执行 Awake (),静态字段会被赋值
- 如果其他类的 Awake () 的执行时机先于这个类的 Awake (),并且在 Awake () 中调用了这个属性,此时静态字段为空,需要使用 FindObjectOfType<T>()(这是 Unity 的 API)找到这个类
- 注:如果需要让辅助线程调用单例类,则必须把单例类的脚本挂载到物体上,并且辅助线程不能在 Awake () 之前调用单例类。否则,辅助线程将会调用 Unity 的 API,但是辅助线程不能调用 Unity 的 API(这是 Unity 的规定)
# 使用
- 继承时必须传递子类类型。
- 在任意脚本生命周期中,通过子类类型访问静态属性
- 为了保证正确的脚本执行顺序,使用自定义的方法代替子类中的 Awake ()
完整代码:
MonoSingleton | using UnityEngine; |
| |
| namespace Common |
| { |
| |
| |
| |
| |
| public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoSingleton<T> |
| { |
| |
| |
| |
| |
| |
| private static T instance; |
| |
| |
| |
| public static T Instance |
| { |
| get |
| { |
| if (instance == null) |
| { |
| instance = FindObjectOfType<T>(); |
| |
| if (instance == null) |
| { |
| |
| new GameObject("Singleton of " + typeof(T)).AddComponent<T>(); |
| } |
| else |
| { |
| instance.Init(); |
| } |
| } |
| |
| return instance; |
| } |
| } |
| |
| private void Awake() |
| { |
| |
| if (instance == null) |
| { |
| instance = this as T; |
| Init(); |
| } |
| } |
| |
| |
| |
| |
| protected virtual void Init() { } |
| } |
| } |