Unity 游戏框架搭建 2018 (二) 单例的模板与最佳实践 背景
很多开发者或者有经验的老手都会建议尽量不要用单例模式,这是有原因的。
单例模式是设计模式中最简单的也是大家通常最先接触的一种设计模式。在框架的设计中一些管理类或者系统类多多少少都会用到单例模式,比如 QFramework 中的 UIMgr,ResMgr 都是单例。当然在平时的游戏开发过程中也会用到单例模式,比如数据管理类,角色管理类等等,以上这些都是非常常见的使用单例的应用场景。
那么今天笔者想好好聊聊单例的使用上要注意的问题,希望大家对单例有更立体的认识,并介绍 QFramework 中单例套件的使用和实现细节。
本篇文章分为四个主要内容:
单例的简介
几种单例的模板实现。
单例的利弊分析。
单例的最佳实践:如何设计一个令人愉快的 API?
单例模式简介可能说有的朋友不太了解单例,笔者先对单例做一个简单的介绍。
定义保证一个类仅有一个实例,并提供一个访问它的全局访问点。
定义比较简洁而且不难理解。
再引用一个比较有意思的例子
俺有6个漂亮的老婆,她们的老公都是我,我就是我们家里的老公 Singleton,她们只要说道“老公”,都是指的同一个人,那就是我(刚才做了个梦啦,哪有这么好的事)。-《泡妞与设计模式》
这个例子非常形象地介绍了我们日常开发中使用单例类的情景,不管在哪里都可以获得同一个并且唯一的单例类的实例。
关于单例模式的简介就到这里,实现的细节和对模式更详尽的介绍网上到处都是,这里不再浪费篇幅。
单例的模板上一篇文章中说到的 Manager of Managers 架构,其中每个 Manager 在 QFramework 中都是由单例实现,当然也可以使用静态类实现,但是相比于静态类的实现,单例更为合适。
如何设计这个单例的模板?先分析下需求,当设计一个 Manager 时候,我们希望整个程序只有一个该 Manager 类的实例,一般马上能想到的实现是这样的:
public class XXXManager { private static XXXManager instance = null; private XXXManager { // to do ... } public static XXXManager() { if (instance == null) { instance = new XXXManager(); } return instance; } }如果一个游戏需要10个各种各样的 manager,那么以上这些代码要复制粘贴好多遍。重复的代码太多!!! 想要把重复的代码抽离出来,怎么办?
答案是引入泛型。
实现如下:
namespace QFramework { public abstract class Singleton<T> where T : Singleton<T> { protected static T mInstance = null; protected Singleton() { } public static T Instance { get { if (mInstance == null) { // 如何 new 一个T??? } return mInstance; } } } }为了可以被继承,静态实例和构造方法都使用了 protect 修饰符。以上的问题很显而易见,那就是不能 new 一个泛型(2016 年 3月9日补充:并不是不能new一个泛型,参考:new 一个泛型的实例,编译失败了,为什么?-CSDN论坛-CSDN.NET-中国最大的IT技术社区),(2016 年 4月5日补充:有同学说可以new一个泛型的实例,不过要求改泛型提供了 public 的构造函数,好吧,这里不用new的原因是,无法显示调用 private 的构造函数)。因为泛型本身不是一个类型,那该怎么办呢?答案是使用反射。
这部分以后可能会复用,所以抽出了 SingletonCreator.cs,专门用来通过反射创建私有构造示例。
实现如下:
SingletonCreator.cs
namespace QFramework { using System; using System.Reflection; public static class SingletonCreator { public static T CreateSingleton<T>() where T : class, ISingleton { // 获取私有构造函数 var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic); // 获取无参构造函数 var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0); if (ctor == null) { throw new Exception("Non-Public Constructor() not found! in " + typeof(T)); } // 通过构造函数,常见实例 var retInstance = ctor.Invoke(null) as T; retInstance.OnSingletonInit(); return retInstance; } } }希望在单例类的内部获得初始化事件所以定制了 ISingleton 接口用来接收单例初始化事件。
ISingleton.cs
namespace QFramework { public interface ISingleton { void OnSingletonInit(); } }Singleton.cs
namespace QFramework { public abstract class Singleton<T> : ISingleton where T : Singleton<T> { protected static T mInstance; static object mLock = new object(); protected Singleton() { } public static T Instance { get { lock (mLock) { if (mInstance == null) { mInstance = SingletonCreator.CreateSingleton<T>(); } } return mInstance; } } public virtual void Dispose() { mInstance = null; } public virtual void OnSingletonInit() { } } }