单例专精(二)SingletonKit与接口
SingletonKit中的接口
SingletonKit
中有一处接口定义,代码如下:
1 | public interface ISingleton |
我们的标准C#单例Singleton<T>
、Mono单例MonoSingleton<T>
都继承了此接口,SingletonProperty<T>
和MonoSingletonProperty<T>
都约束了T
必须继承ISingleton
其中的OnSingletonInit()
规定了一个单例在初始化时,使用此方法来初始化,而不是在单例的构造当中初始化。一个标准C#单例仅仅有一个私有构造函数来做一下限制即可,这个私有构造中不包含初始化的代码。需要注意的是Mono单例不能有构造,其在Awake
中初始化的时间比OnSingletonInit
要早,一般情况下Mono单例不要在Awake里面做初始化。
这种不要在构造函数中初始化,而是在
Init
方法中初始化的思想在QFramework设计阶段就有体现,不论是System、Model、Command,都包含有自己的初始化方法(Init
、Execute
等),有效防止了构造当中初始化造成的死循环问题,通过定义一个初始化方法,并且在框架内部用一个List将这些方法对应的模块实例缓存起来,再依次调用。
为什么要有接口
常见的单例模块,只需要一个类就搞定了。
其使用方式也很简单,只需继承即可,如下所示:
1 | public class MonoSingletonExample : MonoSingleton<MonoSingletonExample>{} |
这种单例工具在需要继承其他类的时候就会收到限制。
比如我们想要MonoSingletonExample
继承一个自己封装的MonoBehaviour
,直接继承肯定是不行的,我们可能需要把自定义的MonoBehaviour
做一个单例版本。
这样做就会导致一些冗余代码产生。
SingletonKit使用MonoSingletonProperty
和SingletonProperty
来解决这个问题
MonoSingletonProperty
和SingletonProperty
使用代码如下
1 | using UnityEngine; |
在以上示例中,ISingleton
是一个标记,它标记了MonoSingletonExample
和SingletonExample
是一个单例,经过标记的类可以通过MonoSingletonProperty
和SingletonProperty
来创建单例。
从技术实现的角度讲,MonoSingletonProperty
和SingletonProperty
是泛型类,我们将其泛型约束为:
1 | public static class SingletonProperty<T> where T : class,ISingleton |
这样就实现了继承了ISingleton
接口的类才能使用MonoSingletonProperty
和SingletonProperty
OnSingletonInit方法
SingletonKit中所有的Singleton,都包含了这个方法的实现(因为它们都继承了ISingleton接口)。
为什么要使用OnSingletonInit
方法初始化呢?
我们可把单例理解为有对象生命周期的静态类,对象的生命周期很简单,就是构造和析构。单例是有创建和销毁的过程的。
而静态类是全局唯一的,虽然单例不是静态类,但是作用和静态类差不多,全局访问,并且全局唯一。
一个对象的声明周期从创建实例开始,一般情况下,我们从构造函数中初始化。
这个在普通C#应用平台(.Net、Xamarin)没什么问题。但是在Unity中,我们在一个普通C#单例类(或者是普通C#类)的构造函数里面是不能调用Unity的一些API的(比如new GameObject
、Application.isPlaying
等),这是因为Unity不能控制普通C#类的生命周期。
如果指定在OnSingletonInit
去写一些初始化代码,就可以避免以上问题出现。而且我们也不用纠结初始化代码应该写在哪里,统统写在OnSingletonInit
里就好了。