在面试过程中被问到用过哪些设计模式的问题应该很常见。而单例模式作为最简单最常用的应该是第一个被想到的。开篇就拿单例模式”开刀”吧。
什么是单例模式?
单例模式就是 用来保证一个类只有一个实例,并向外部提供一个全局访问点。 如:游戏中的对话框,不能每次显示都要创建出来一个,关了就销毁吧。
这也体现出了单例模式的如下优点:全局一份实例,减少内存占用,也减少了资源的频繁创建销毁。
如何写一个单例模式呢?如游戏中的一个XXXManager
1 | public class XXXManager |
单例模式的分类
饿汉式。如上面的例子,在类加载的时候不管用到用不到就会创建实例。
优点:线程安全。
缺点:比较浪费内存。懒汉式。只有在调用到的时候才会创建实例。比较节约内存。多线程可能会出问题。
优点:节约内存。
缺点:单线程没问题,多线程时非线程安全(但可以解决)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class XXXManager
{
private static XXXManager _instence = null;
private XXXManager(){}
// 调用的时候才创建实例
public static XXXManager Singleton
{
get
{
if (_instence == null)
_instence = new XXXManager();
return _instence;
}
}
}
加锁解决懒汉式的非线程安全问题
由于Unity是单线程运行,可以不考虑多线程下非线程安全问题。但是要注意在其他地方使用的时候。
1 | private static object mLock = new object(); |
每个单例都要重新写一遍,好麻烦?
一个工程中可能有很多类都要用到单例,总不能每次都重新写一遍吧,这就导致有很多重复的代码。可以使用泛型 将单例抽象出来。
但是_instence
对象如何创建呢?直接使用 _instence=new T()
是会报错的。原因是T必须要有可以new的约束,解决方案:
- 给T添加new() 约束
- 使用反射(下面是反射实现)使用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19abstract class NormalSingleton<T> where T:NormalSingleton<T>
{
protected static T _instence = null;
protected NormalSingleton(){}
public static T Singleton
{
get
{
if (_instence == null)
{
//_instence = new T();
//上面这句话是会报错的,因为要求T是可以new出来的类型。
_instence = Activator.CreateInstance<T>();
}
return _instence;
}
}
}1
2
3
4
5
6
7
8
9
10//直接继承 NormalSingleton
public class XXXManager : NormalSingleton<XXXManager>
{
private XXXManager(){}
public void AAA(){}
}
// 调用
XXXManager.Singleton.AAA();
那些不能通过new的类(如继承monobehavior的类)如何使用单例模板?
我们知道继承了Monobehavior的脚本只能挂载到GameObject上使用。上面的方式就不能使用了。
首先,要想实现Mono的单例,就要保证:
- 全局只有一个GaneObject挂载该Mono脚本,并且只能挂载一个。
- 可以接受Mono脚本的生命周期,以及单例的销毁
代码时实现
1 | public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour |
使用只需要继承MonoSingleton
即可。