在这里插入图片描述

1.什么是单例模式

相信我们都遇到过这样的问题:

  1. 我们使用new创建对象,这个对象需要全局共享只用这一个
  2. 我们需要用一种简单的方式全局能访问一个类创建的唯一对象

总结下来,我们经常需要创建一个类的单例,也就是创建单个实例供全局访问。

2.单例模板

我们试图创建一种全局都可以访问的对象,拥有着简单的语法,像这样

class GameManager: Singleton<GameManager>
{
	int val = 10;
	public void Hi()
	{
		val++;
		print(val);
	}
}
//文件1
void TestMethod1()
{
	GameManager.Instance.Hi();
}
//文件2
void TestMethod2()
{
	GameManager.Instance.Hi();
}

我们按顺序调用TestMethod1和2.输出如下

11
12

我们来剖析单例模板下的奥秘,在代码里,我添加了较为详细的注释,而且单例模式作为最简单的设计模式,理解起来相当容易。

using System;

namespace AirFramework
{
    public class Singleton<T> where T : Singleton<T>
    {
        private static readonly object _instanceLock = new object();

        //volatile防止线程优化,多线程可能会对值的写入时机进行微调,导致冲突
        private volatile static T instance;
		
        private static bool initialized = false;
        public static bool Initialized => initialized;
        public static T Instance
        {
            get
            {
                //Double Check实现线程安全并提高性能,大多数时候不需要进入if内
                if (instance == null)
                {
                	//如果已经有线程进入,等待
                    lock (_instanceLock)
                    {
                        if (instance == null && !initialized)
                        {

                            instance = Activator.CreateInstance<T>();
                            initialized = true;
                        }
                    }
                }
                return instance;
            }
        }

        public static void Load() 
        {
            _ = Instance;
        }
        public static void Unload()
        {
            if (instance != null)
            {
                initialized = false;
                instance = null;
            }
        }
        //防止被new
        protected Singleton() { }
    }
}

同样的,我们对于MonoBehaviour类型的单例
也是类似的想法,但是不同的是mono类型不能使用new,而是采用addcomponent

using UnityEngine;
namespace AirFramework
{
    public class SingletonMono<T> : MonoBehaviour where T : SingletonMono<T>
    {
        private static T instance = null;
        private static bool initialized = false;
        public static bool Initialized => initialized;
        private static bool autoReset = false;
        //Set this value will reset the Instance
        public static bool AutoReset
        {
            get => autoReset;
            set
            {
                if (value)
                {
                    Unload();
                }
                autoReset = value;
            }
        }
        public static T Instance
        {
            get
            {
                //不保证线程安全
                if (instance == null && !initialized)
                {
                    GameObject container = new GameObject();
                    container.name = "Mono(" + typeof(T) + ")";
                    instance = container.AddComponent<T>();
                    if (!autoReset) DontDestroyOnLoad(container);
                    initialized = true;
                }
                return instance;
            }
        }
        public static void Load()
        {
            _ = Instance;
        }
        public static void Unload()
        {
            if (instance != null)
            {
                Destroy(instance.gameObject);
                initialized = false;
                instance = null;
            }
        }
        protected SingletonMono() { }
    }
}

但是这种单例模式,无法应对某些特殊情况,比如继承

假定存在GameManager类,BaseManager类,需要把GameManager做成单例,但是要继承BaseManager

我们可以发现继承单例模板和继承BaseManager冲突了
尽管这种继承式的单例是一种很脏的设计,但是没有最好的设计,只有最合适的。我们仍然要想办法解决
引出一种交单例属性的设计
这样就可以非侵入的实现这种很脏的设计

class BaseManager{}
class GameManager:BaseManager
{
	public static GameManager Instance
	{
		get=>SingletonProperty<GameManager>.Instance;
	}
}

下面是单例属性的模板,其实并没有太大差异

using System;

namespace AirFramework
{
    public class SingletonProperty<T> where T : class
    {
        private static readonly object _instanceLock = new object();

        //volatile防止线程优化
        private volatile static T instance;

        private static bool initialized = false;
        public static bool Initialized => initialized;
        public static T Instance
        {
            get
            {
                //Double Check实现线程安全
                if (instance == null)
                {
                    lock (_instanceLock)
                    {
                        if (instance == null && !initialized)
                        {

                            instance = Activator.CreateInstance<T>();
                            initialized = true;
                        }
                    }
                }
                return instance;
            }
        }

        public static void Load()
        {
            _ = Instance;
        }
        public static void Unload()
        {
            if (instance != null)
            {
                initialized = false;
                instance = null;
            }
        }
        protected SingletonProperty() { }

    }
}

缺陷

很多人都在讲“少用单例’,不用单例这种话,少有人思考为什么
大家一传十十传百形成了一种默契,绕的人头晕。

1.生命周期不可控
很多时候我们的单例是只有访问时才会加载的,属于懒汉式,这个单例的生命什么时候结束脱离了GC的掌控,只能靠我们自己去释放,如果忘记释放或者提前被释放容易产生各种问题,(其他开发者不知道你什么时候释放单例,所以就无法释放)
2.作用域过广
我们在全局都能通过静态访问单例,容易让用户误操作,而且让代码散乱不安全,如果有100个单例…这都是什么
3.单一职责过于复杂
使用单例可能会让一个类复杂多种职责,容易让单一文件过大,代码复杂性上升,一个GameManager统计分数还计时,还存玩家信息,又存地图信息…改起来亚历山大

我们应该辩证的对待单例,而非一味否定。
我觉得单例的问题主要是基于这三点,我们可以选择性的使用单例,比如第一点要注意单例的生命,只能靠自己去尽量克服,而第二点可以通过类的访问权限来限制外部不能访问单例类,第三点是单例违法设计原则的地方,适当的违反设计原则在中小项目会更方便高效。