1. 什么是单例模式
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
2. 单例模式的类型
单例模式有两种类型:
懒汉式:在真正需要使用对象时才去创建该单例类对象
懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。
public class Singleton
{
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这个方法其实是存在问题的,试想一下,如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就不符合单例模式只有一个实例对象的要求。所以在实例化对象时加锁。
public static Singleton getInstance()
{
//线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton
if (singleton == null)
{
// 线程A或线程B获得该锁进行初始化
synchronized(Singleton.class)
{
//其中一个线程进入该分支,另外一个线程则不会进入该分支
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return singleton;
}
}
注意上面的代码在第3行已经实例化好了一个Singleton对象在内存中,不会有多个Singleton对象实例存在类在加载时会在堆内存中创建一个Singleton对象,当类被卸载时,Singleton对象也随之消亡了。
Unity中的单例模式
Unity中各种Manager常用懒汉式单例模式设计。一般会创建一个泛型单利模式的BaseManager,让其他管理类继承BaseManager实现单例模式Manager。
不继承MonoBehaviour的单例模式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//泛型单例模式基类
public class BaseManager<T> where T:class,new() //泛型约束,让子类必须实现公共无参构造函数
{
private static T instance;
public static T Instance
{
get
{
if(instance==null)
{
instance = new T();
}
return instance;
}
}
}
//创建UIManager继承BaseManager
public class UIManager:BaseManager<UIManager>
{
public UIManager()
{
}
}
不继承MonoBehaviour的BaseManager需要在子类中自定义公共类型的无参构造函数,不需要挂载到对象上。可以在其他脚本中通过new实例化出来(缺点,违反了单例模式)。为了解决这个问题,使用反射来优化,让构造函数设置为私有,不能再外部访问。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BaseManager<T> where T:class
{
private static T instance;
public static T Instance
{
get
{
if(instance==null)
{
//获取到T类的信息
Type type = typeof(T);
//获取到T类的非公共构造函数(成员构造函数,非公共构造函数,绑定对象,参数类型,参数列表)
ConstructorInfo info = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
if(info!=null)
{
//调用构造函数
instance = info.Invoke(null) as T;
}
else
{
Debug.Log("没有无参构造函数");
}
}
return instance;
}
}
}
//创建UIManager继承BaseManager
public class UIManager:BaseManager<UIManager>
{
//子类需要实现私有构造函数
private UIManager()
{
}
}
继承MonoBehaviour的单例模式
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//自动挂载脚本
public class BaseManager<T> : MonoBehaviour where T:MonoBehaviour//泛型约束,让子类必须继承MonoBehaviour
{
private static T instance;
public static T Instance
{
get
{
if(instance==null)
{
//创建一个对象
GameObject obj = new GameObject();
//将该脚本挂载到对象上
instance = obj.AddComponent<T>();
//为对象取名为当前类的名字
obj.name = typeof(T).ToString();
//跨场景不销毁对象
DontDestroyOnLoad(obj);
}
return instance;
}
}
}
//创建UIManager继承BaseManager
public class UIManager:BaseManager<UIManager>
{
void Start()
{
}
void Update()
{
}
}
继承MonoBehaviour的BaseManager子类不需要声明无参构造函数,需要挂载到对象上,但不能被实例化。可以执行Start,Update等生命周期函数和协同程序。
总结
单例模式是一种常见的设计模式,它让一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式适用于需要全局唯一实例的场景,如日志记录器、配置管理类等。在使用单例模式时,需要注意线程安全问题,以及避免出现多次实例化问题。