您的位置:首页 > 移动开发 > Unity3D

Unity3d中C#单例模式实现

2017-03-06 19:22 344 查看


为什么要使用单例模式

在我们的整个游戏生命周期当中,有很多对象从始至终有且只有一个。这个唯一的实例只需要生成一次,并且直到游戏结束才需要销毁。

单例模式一般应用于管理器类,或者是一些需要持久化存在的对象。


Unity3d中单例模式的实现方式


(一)c#当中实现单例模式的方法

因为单例本身的写法不是重点,所以这里就略过,直接上代码。

以下代码来自于MSDN。

[csharp] view plain copy print?



public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}


public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}


以上代码是比较完整版本的c#单例。在unity当中,如果不需要使用到monobeheviour的话,可以使用这种方式来构建单例。


(二)如果是MonoBeheviour呢?

MonoBeheviour和一般的类有几个重要区别,体现在单例模式上有两点。

第一,MonoBehaviour不能使用构造函数进行实例化,只能挂载在GameObject上。

第二,当切换场景时,当前场景中的GameObject都会被销毁(LoadLevel带有additional参数时除外),这种情况下,我们的单例对象也会被销毁。

为了使之不被销毁,我们需要进行DontDestroyOnLoad的处理。同时,为了保持场景当中只有一个实例,我们要对当前场景中的单例进行判断,如果存在其他的实例,则应该将其全部删除。

因此,构建单例的方式会变成这样。

[csharp] view plain copy print?



public sealed class SingletonMoBehaviour: MonoBehaviour
{
private static volatile SingletonBehaviour instance;
private static object syncRoot = new Object(); &
4000
nbsp;
public static SingletonBehaviour Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null) {
SingletonBehaviour[] instances = FindObjectsOfType<SingletonBehaviour>();
if (instances != null){
for (var i = 0; i < instances.Length; i++) {
Destroy(instances[i].gameObject);
}
}
GameObject go = new GameObject(“_SingletonBehaviour”);
instance = go.AddComponent<SingletonBehaviour>();
DontDestroyOnLoad(go);
}
}
}
return instance;
}
}
}


public sealed class SingletonMoBehaviour: MonoBehaviour
{
private static volatile SingletonBehaviour instance;
private static object syncRoot = new Object();
public static SingletonBehaviour Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)  {
SingletonBehaviour[] instances = FindObjectsOfType<SingletonBehaviour>();
if (instances != null){
for (var i = 0; i < instances.Length; i++) {
Destroy(instances[i].gameObject);
}
}
GameObject go = new GameObject("_SingletonBehaviour");
instance = go.AddComponent<SingletonBehaviour>();
DontDestroyOnLoad(go);
}
}
}
return instance;
}
}
}


这种方式并非完美。其缺陷至少有:

* 如果有许多的单例类,会需要复制粘贴这些代码

* 有些时候我们也许会希望使用当前存在的所有实例,而不是删除全部新建一个实例。(这个未必是缺陷,只是设计的不同)

在本文后面将会附上这种单例模式的代码以及测试


(三)使用模板类实现单例

为了避免重复代码,我们可以使用模板类的方式来生成单例。非MonoBehaviour的实现方式这里就不赘述,只说monoBehaviour的。

代码

[csharp] view plain copy print?



public sealed class SingletonTemplate<T> : MonoBehaviour where T : MonoBehaviour { private static volatile T instance; private static object syncRoot = new Object(); public static T Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) { T[] instances = FindObjectsOfType<T>(); if (instances != null) { for (var i = 0; i < instances.Length; i++) { Destroy(instances[i].gameObject); } } GameObject go = new GameObject(); go.name = typeof(T).Name; instance = go.AddComponent<T>(); DontDestroyOnLoad(go); } } } return instance; } } }


public sealed class SingletonTemplate<T> : MonoBehaviour where T : MonoBehaviour
{
private static volatile T instance;
private static object syncRoot = new Object();
public static T Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
T[] instances = FindObjectsOfType<T>();
if (instances != null)
{
for (var i = 0; i < instances.Length; i++)
{
Destroy(instances[i].gameObject);
}
}
GameObject go = new GameObject();
go.name = typeof(T).Name;
instance = go.AddComponent<T>();
DontDestroyOnLoad(go);
}
}
}
return instance;
}
}
}


以上代码解决了每个单例类都需要重复写同样代码的问题,基本上算一个比较好的解决方案。


单例当中的一些坑

最大的坑是单例的monobehaviour,其生命周期并非我们程序员可以控制的。MonoBehaviour本身的Destroy,将会决定单例类的实例在何时销毁。因此,一定不要在OnDestroy函数中调用单例对象,这可能导致该对象在游戏结束后依然存在(原本的单例类已经销毁了,你又创建了一个新的,当然就不会再销毁一次了)。举例来说,以下的代码是需要注意的的。

[csharp] view plain copy print?



void Start(){ Singleton.Instance.OnSomeTime += DoSth; } void OnDestroy(){ Singleton.Instance.OnSomeTime -= DoSth; }


void Start(){
Singleton.Instance.OnSomeTime += DoSth;
}

void OnDestroy(){
Singleton.Instance.OnSomeTime -= DoSth;
}


此外,建议不要在场景或者预置当中放置拥有单例类组件的Gameobject。很多网上的项目有这样的写法。但我的观点是这种写法不够灵活。如果使用这种方法,注意在获取instance时,将找到的第一个对象赋给instance

[csharp] view plain copy print?



public static T Instance
{
get
{
if (instance == null)
{
T[] instances = FindObjectsOfType<T>();
if (instances != null)
{
instance = instances[0];
for (var i = 1; i < instances.Length; i++)
{
Destroy(instances[i].gameObject);
}
}
}
return instance;
}
}


public static T Instance
{
get
{
if (instance == null)
{
T[] instances = FindObjectsOfType<T>();
if (instances != null)
{
instance = instances[0];
for (var i = 1; i < instances.Length; i++)
{
Destroy(instances[i].gameObject);
}
}
}
return instance;
}
}



单例与静态的区别

我们都知道,静态的成员或者方法,在整个Runtime当中也只有一份。所以一直存在着静态与单例模式之争。

事实上这两种方式都有其适用范围,不能片面的说某种好或某种不好。具体的争论实在是太多了,资料也多,这里也不深入讲,仅仅简单的说明一下两者使用上的区别。

* 单例的方法可以继承,静态的不可以。

* 单例存在着创建实例的过程,生命周期并不是整个运行时,静态方法在编译时就存在,整个过程中是一直有效的。

虽然两者的区别其实非常多,但在这里只说一个最核心的问题,如何进行选择?

其实很简单,从面向对象的角度来说——

* 如果方法中需要用到实例本身的状态,也就是说需要用到实例的成员时,这个方法一定是实例方法,请使用单例调用。

* 如果方法中完全不涉及到实例,而是类共享的一些状态的话,或者甚至不需要任何状态,这个方法一定是静态方法。

从应用的角度来说,我觉得以上就足够了,至于说内存占用的不同啊,GC以及效率上的区别啊这些我觉得更多是理论,不够贴近实际使用。


单例虽好,请勿滥用

滥用设计模式是很多人都会遇到的问题,尤其是对新手来说。设计模式应该只在合适的场景当中使用,而不是随处都使用单例。

事实上,单例的滥用会造成以下一些问题:

* 代码的耦合性可能会增加。如一个模块当中调用MusicController.instance.Play,可能导致这个模块无法独立复用。

* 单个类的职责可能会过大,违背单一职责原则。

* 某些情况下会造成一些性能问题。因为单例的对象永远不销毁,过多的单例会造成性能问题。

可以使用一些别的方法来代替单例模式,这里暂时不再扩展。


单例的单例

在某些情况下我会使用这种方法来构建唯一实例。 即在总单例类中声明了初始化其他的子单例类,方便了单例的统一获取和初始化。

获取某个子单例的实例,可以用GameRoot.Instance.dbManager或DBManager.Instance。

作为更高一级的控制器的单例成员或者类变量,同样可以使该实例在整个游戏中仅存在一份。

其优势在于扩展性更好,因为我们可以随时添加单例的Controller类,等等。这里就不再扩展了。

[csharp] view plain copy print?



using UnityEngine;

public class GameRoot : MonoBehaviour {

//数据读取管理类
[HideInInspector]
public DBManager dbManager;

//页面管理器
[HideInInspector]
public PageManager pageManager;

private static object _lock = new object();
private static GameRoot _instance;
public static GameRoot Instance
{
get
{
lock (_lock)
{
if (_instance == null)
{
GameObject go = new GameObject(“GameRoot”);
_instance = go.AddComponent<GameRoot>();
}
}
return _instance;
}
}
private void Awake()
{
if (_instance == null)
{
_instance = this;
_instance.Initialize();
}
else
{
Destroy(this);
_instance = null;
}
DontDestroyOnLoad(this);
}

void Initialize()
{
dbManager = gameObject.AddComponent<SqlManager>();
dbManager.Init();

pageManager = gameObject.AddComponent<PageManager>();
pageManager.Init();
}
}


using UnityEngine;

public class GameRoot : MonoBehaviour {

//数据读取管理类
[HideInInspector]
public DBManager       dbManager;

//页面管理器
[HideInInspector]
public PageManager      pageManager;

private static object _lock = new object();
private static GameRoot _instance;
public static GameRoot Instance
{
get
{
lock (_lock)
{
if (_instance == null)
{
GameObject go = new GameObject("GameRoot");
_instance = go.AddComponent<GameRoot>();
}
}
return _instance;
}
}
private void Awake()
{
if (_instance == null)
{
_instance = this;
_instance.Initialize();
}
else
{
Destroy(this);
_instance = null;
}
DontDestroyOnLoad(this);
}

void Initialize()
{
dbManager = gameObject.AddComponent<SqlManager>();
dbManager.Init();

pageManager = gameObject.AddComponent<PageManager>();
pageManager.Init();
}
}


DBManager单例类:

[csharp] view plain copy print?



public class DBManager : MonoBehaviour { private static DBManager _instance = null; public static DBManager Instance { get { if (_instance == null) { _instance = GameRoot.Instance.dbManager; } return _instance; } } }


public class DBManager : MonoBehaviour {

private static DBManager _instance = null;
public static DBManager Instance
{
get
{
if (_instance == null)
{
_instance = GameRoot.Instance.dbManager;
}
return _instance;
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity3d c#