UF_OLDL1——单例模式基类

单例模式

一般的单例模式差不多都会写类似于下面的这种形式,区别几乎只在于类名

1
2
3
4
5
6
public class GameManager
{
private static GameManager instance = new GameManager(); //为该类写一个私有静态变量,并调用私有构造函数,装载该类的唯一的实例化对象
public static GameManager Instance => instance; //为该类写一个静态属性,使唯一装载该类的实例化对象的变量在外部只读,以可以调用该类的各种成员
private GameManager() { } //私有的无参构造函数,确保只有该类的私有静态变量可以构造该类的实例化对象,而外部不能,在外部第一次调用静态属性时会执行
}

或者用静态方法来也可以获取私有静态变量

1
2
3
4
5
6
7
8
9
public class BaseManager
{
private static BaseManager instance;
public static BaseManager Instance()
{
if (instance == null) instance = new BaseManager();
return instance;
}
}

如果是继承MonoBehaviour的类,不能new(),如何让私有静态变量装载该类呢
在Awake()里instance = this即可

1
2
3
4
5
6
7
8
9
public class GameManager : MonoBehaviour
{
private static GameManager instance; //为该类写一个私有静态变量,并调用私有构造函数,装载该类的唯一的实例化对象
public static GameManager Instance => instance; //为该类写一个静态属性,使唯一装载该类的实例化对象的变量在外部只读,由此可以调用该类的各种成员
private void Awake()
{
instance = this; //该脚本对象一创建就让私有静态变量等于自己,这样就可以使外部通过静态属性调用唯一的类对象
}
}

单例模式可以确保该类只能有一个实例化对象,且只能通过静态属性调用它的成员
类似于 GameManager.Instance.____ 这样的形式

这种模式尤其适合只需要在程序里存在一个对象的类,例如各种管理器

与静态类的区别

静态类也是只能用类名调用且不能实例化的类,它和单例模式类有什么区别呢?

  • 静态类的方法适合纯数据处理,不依赖其它对象实例,写入参数就可以,但这也是它的限制,方法里是不可调用它的实例对象的。
  • 单例模式,看起来也不用实例化,但实际第一次调用时它在内部创建了实例化对象,之后都是在调用这个实例,他的方法中就可以调用其它实例方法了。
  • 静态类不能过多,他会一开始就需要将方法逻辑编译成本机代码(win,mac,linux),
    这就会在程序启动时形成过多cpu计算。而实例是在实例方法第一次被调用时才编译成本机代码(只编译一次)。
  • 所以应该尽量使用实例,有更好的内存管理gc释放,也会分散编译方法体的本机代码

单例模式管理器基类

涉及知识点:泛型,单例模式

作用:减少单例模式重复代码的书写

使用方法:
需要作为单例模式的类直接继承该方法,泛型参数填入自己的类,该类即可当作单例模式使用

需要注意的是,这里并没有用私有构造函数来限制外部实例化单例,这里需要注意(由于约束,这里并不方便使用私有构造函数)

1
2
3
4
5
6
7
8
9
10
11
public abstract class BaseManager<T> where T : new()    //使用泛型方法,使派生类中的私有变量和返回方法能够转载和返回自己类型的类对象
{
private static T instance; //为该类写一个私有静态变量,并调用私有构造函数,装载该类的唯一的实例化对象
public static T Instance() //这里外部获取使用的是方法而非属性,因为如果之后要加一些锁或其它功能的话,这样更方便修改!
{
if (instance == null) //如果私有变量没有自己类的类对象,就先使用构造方法实例化一个,因此这里只能用公开无参构造方法约束
instance = new T();
return instance;
}
//由于使用了公开无参构造方法约束,这里不再可以使用私有构造方法,但因此外部可能会构造该类的对象,因此需要注意
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GameManager : BaseManager<GameManager>
{
public void UseMe()
{
Debug.Log("我是单例模式");
}
}

public class Test
{
void Main()
{
GameManager.Instance().UseMe();
}
}

继承MonoBehaviour的单例模式基类

继承了MonoBehaviour的类,不能直接new(),只能通过拖曳到对象上或GameObject.AddComponent<>()来构造
因此需要在Awake()里让私有静态变量等于自己

使用方法:
需要作为单例模式的类直接继承该方法,泛型参数填入自己的类,该类即可当作单例模式使用

第一种实现方式

需要注意的是,这个基类并不能保证类对象只能被创建一次,如果多次创建,则静态方法只能获取到最后一次构建的类对象
而单例模式的唯一性也会遭到破坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;

public class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance()
{
return instance;
}

protected void Awake()
{
//该脚本对象一创建就让私有静态变量等于自己,这样就可以使外部通过静态属性调用唯一的类对象
instance = this as T;
}
}

第二种实现方式

这个改造避免了我们手动向某个对象添加脚本,当静态私有变量没有该类对象时,自动添加一个挂载该脚本的空对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;

public class SingletonAutoMono<T> : MonoBehaviour where T : MonoBehaviour
{
private static T instance;
public static T Instance()
{
//这个改造避免了我们手动向某个对象添加脚本,当静态私有变量没有该类对象时,自动添加一个挂载该脚本的空对象
if (instance == null)
{
GameObject obj = new GameObject(); //在场景上创建一个空对象
obj.name = typeof(T).ToString(); //将该对象名的名字改为自己类的名字,也就是脚本名
DontDestroyOnLoad(obj); //防止该单例模式对象在切换场景后被销毁
instance = obj.AddComponent<T>(); //向该对象添加自己脚本,并让私有静态变量等于自己
}
return instance;
}
}