2016-12-15 13:06:29 qq563129582 阅读数 13631

为什么要使用单例模式

在我们的整个游戏生命周期当中,有很多对象从始至终有且只有一个。这个唯一的实例只需要生成一次,并且直到游戏结束才需要销毁。 
单例模式一般应用于管理器类,或者是一些需要持久化存在的对象。

Unity3d中单例模式的实现方式

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

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

以下代码来自于MSDN。

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的处理。同时,为了保持场景当中只有一个实例,我们要对当前场景中的单例进行判断,如果存在其他的实例,则应该将其全部删除。 

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

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的。 
代码

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函数中调用单例对象,这可能导致该对象在游戏结束后依然存在(原本的单例类已经销毁了,你又创建了一个新的,当然就不会再销毁一次了)。举例来说,以下的代码是需要注意的的。
void Start(){
    Singleton.Instance.OnSomeTime += DoSth;
}

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


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

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单例类:


public class DBManager : MonoBehaviour {

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


2015-07-19 22:12:15 yuechuzhao 阅读数 3537

为什么要使用单例模式

在我们的整个游戏生命周期当中,有很多对象从始至终有且只有一个。这个唯一的实例只需要生成一次,并且直到游戏结束才需要销毁。
单例模式一般应用于管理器类,或者是一些需要持久化存在的对象。

Unity3d中单例模式的实现方式

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

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

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

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

(二)如果是monobeheviour呢?

monobeheviour和一般的类有几个重要区别,体现在单例模式上有两点。
第一,monohehaviour不能使用构造函数进行实例化,只能挂载在GameObject上。
第二,当切换场景时,当前场景中的GameObject都会被销毁(LoadLevel带有additional参数时除外),这种情况下,我们的单例对象也会被销毁。
为了使之不被销毁,我们需要进行DontDestroyOnLoad的处理。同时,为了保持场景当中只有一个实例,我们要对当前场景中的单例进行判断,如果存在其他的实例,则应该将其全部删除。
因此,构建单例的方式会变成这样。

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

                } 
            } 
            return instance; 
        } 
    } 
} 

这种方式并非完美。其缺陷至少有:
* 如果有许多的单例类,会需要复制粘贴这些代码
* 有些时候我们也许会希望使用当前存在的所有实例,而不是删除全部新建一个实例。(这个未必是缺陷,只是设计的不同)
在本文后面将会附上这种单例模式的代码以及测试。

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

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

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 = (T[])FindObjectsOfType(typeof(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函数中调用单例对象,这可能导致该对象在游戏结束后依然存在(原本的单例类已经销毁了,你又创建了一个新的,当然就不会再销毁一次了)。举例来说,以下的代码是需要注意的的。
void Start(){
    Singleton.Instance.OnSomeTime += DoSth;
}

void OnDestroy(){
    Singleton.Instance.OnSomeTime -= DoSth;
}
  • 此外,建议不要在场景或者预置当中放置拥有单例类组件的Gameobject。很多网上的项目有这样的写法。但我的观点是这种写法不够灵活。如果使用这种方法,注意在获取instance时,将找到的第一个对象赋给instance
if (instance == null)  {
                        T[] instances = (T[])FindObjectsOfType(typeof(T));
                        if (instances != null){
                        instance = instances[0];
                            for (var i = 1; i < instances.Length; i++) {
                                Destroy(instances[i].gameObject);
                            }
                        }
    }

单例与静态的区别

我们都知道,静态的成员或者方法,在整个Runtime当中也只有一份。所以一直存在着静态与单例模式之争。
事实上这两种方式都有其适用范围,不能片面的说某种好或某种不好。具体的争论实在是太多了,资料也多,这里也不深入讲,仅仅简单的说明一下两者使用上的区别。
* 单例的方法可以继承,静态的不可以。
* 单例存在着创建实例的过程,生命周期并不是整个运行时,静态方法在编译时就存在,整个过程中是一直有效的。
虽然两者的区别其实非常多,但在这里只说一个最核心的问题,如何进行选择?


其实很简单,从面向对象的角度来说——
* 如果方法中需要用到实例本身的状态,也就是说需要用到实例的成员时,这个方法一定是实例方法,请使用单例调用。
* 如果方法中完全不涉及到实例,而是类共享的一些状态的话,或者甚至不需要任何状态,这个方法一定是静态方法。
从应用的角度来说,我觉得以上就足够了,至于说内存占用的不同啊,GC以及效率上的区别啊这些我觉得更多是理论,不够贴近实际使用。

单例虽好,请勿滥用

滥用设计模式是很多人都会遇到的问题,尤其是对新手来说。设计模式应该只在合适的场景当中使用,而不是随处都使用单例。
事实上,单例的滥用会造成以下一些问题:
* 代码的耦合性可能会增加。如一个模块当中调用MusicController.instance.Play,可能导致这个模块无法独立复用。
* 单个类的职责可能会过大,违背单一职责原则。
* 某些情况下会造成一些性能问题。
可以使用一些别的方法来代替单例模式,这里暂时不再扩展。

不使用单例的单例

在某些情况下我会使用这种方法来构建唯一实例。
Game.Instance.MusicController或Game.MusicController。
作为更高一级的控制器的单例成员或者类变量,同样可以使该实例在整个游戏中仅存在一份。
其优势在于扩展性更好,因为我们可以随时添加Game.Instance.ReleaseMusicController,等等。这里就不再扩展了。
本文的代码如下
singleton

2016-11-18 23:07:00 u012322710 阅读数 2222

原创


题外话:今天遇到一个坑爹的事,新来了一个WIN10的电脑,但是Unity装完,任何IDE工具打开都无法识别命名空间,原来WIN10本来不带NET2.0,需要去控制面板--程序和功能---添加.net3.5


单例模式

初学者很容易搞晕,为啥有2种写法,这里顺带着写一下,不是本章的要点。

首先,是继承自MonoBehaviour的单例,需要使用U3D组件和功能可以用这种单例。理解U3D本身单例写法的机制就知道为啥要这么写了,第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化该类了。

    public static ObjectPool instance;   //单例

    //U3D的单例机制,是第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化instance了。
    void Awake()
    {
        instance = this;
    }


其次,不继承MonoBehaviour的单例,全局单例吧,什么场景都能用,不能挂载到U3D物体上。

    private static ObjectPool instance;   //单例

    public ObjectPool GetInstance()
    {
        if (instance == null) 
        {
            instance = new ObjectPool();
        }

        return instance;
    }  


对象池

思路:重复创建大量物理和销毁物体,会大量消耗资源,比如:子弹,金币等等,对象池的作用就是创建完了不销毁,只能把它隐藏存入对象池,用一个列表保存数据,需要用的时候再取出来,同时激活它,并且移除列表,这样列表中剩下的就是隐藏可以使用的对象。因为可能有很多物体需要用对象池,所以把个物体的LIST列表存入一个字典。如果对象池中有100个对象,创建90个都是在对象池中激活,只有创建110,才会再新生成10个。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;



//U3D单例对象池
public  class ObjectPool : MonoBehaviour
{
    public static ObjectPool instance;   //单例

    public GameObject[] prefabObjects;   //prefab数组

    private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();  //对象池字典


    //U3D的单例机制,是第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化instance了。
    void Awake()
    {
        instance = this; 
    }


    //从池中获取      //正式项目最好用一个静态类来保存名字,防止出错
    public GameObject GetOut(string GameObjectName, Vector3 vector3)
    {
        GameObject gameObject;   //返回的gameObject

        //如果池中有
        if (pool.ContainsKey(GameObjectName) && pool[GameObjectName].Count > 0)
        {
            //取池里的用
            gameObject = pool[GameObjectName][0];
            gameObject.SetActive(true);
            gameObject.transform.position = vector3;

            //取完移除
            pool[GameObjectName].RemoveAt(0);
        }
       //如果没有
        else
        {
            GameObject prefabObject = null;

            //要生成的prefabObject   prefab数组中的物体名字要和传入的字符串一致  
            for (int i = 0; i < prefabObjects.Length; i++)
            {
                if (prefabObjects[i].name == GameObjectName)
                {
                    prefabObject = prefabObjects[i];
                }
            }

            //直接创建
            gameObject = (GameObject)GameObject.Instantiate(prefabObject, vector3, Quaternion.identity);
        }
       
        return gameObject;
    }

    //存入对象池
    public void SetIn(string GameObjectName,GameObject gameObject) 
    {
      //池中没有
      if (!pool.ContainsKey(GameObjectName))
      {   //新建池List
          pool.Add(GameObjectName, new List<GameObject>());
      }
      
      //存入池
      gameObject.SetActive(false);
      pool[GameObjectName].Add(gameObject);

      Debug.Log(pool[GameObjectName].Count);

    }

    //销毁对象池
    public void DestroyPool(string GameObjectName)
    {
        if (pool.ContainsKey(GameObjectName))
        {   //删除对象
            for (int i = 0; i< pool[GameObjectName].Count; i++) 
            {
                Destroy(pool[GameObjectName][i]);
            }

            //移除列表
            pool.Remove(GameObjectName);
        }
    }
   
}


注意:使用的时候, 从对象池获取,什么地方都可以用,存入对象池这个方法最好在物体本身上,比如子弹上挂着一个,自己就把自身存入对象池。实际使用就用PoolManager  插件好了,据说很好用。

2019-08-30 13:59:32 weixin_44350205 阅读数 26

单例模式是所有设计模式之中运用最广泛的设计模式之一,而对象池在游戏中相当常用,通过对GameObject的反复利用,能节约宝贵的CPU资源。在本篇文章中将给大家分享下在项目中使用单例模式与对象池的技巧。

 

 

单例模式

 

单例模式,简单说就是类的实例在内存中只存在一份,其中单例模式有2种写法:

 

首先,是继承自MonoBehaviour的单例,需要使用U3D组件和功能可以用这种单例。理解U3D本身单例写法的机制就知道为啥要这么写了,第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化该类了。

    public static ObjectPool instance;   //单例
    //U3D的单例机制,是第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化instance了。
    void Awake()
    {
        instance = this;
    }

 

其次,不继承MonoBehaviour的单例,全局单例吧,什么场景都能用,不能挂载到U3D物体上。

    private static ObjectPool instance;   //单例
    public ObjectPool GetInstance()
    {
        if (instance == null) 
        {
            instance = new ObjectPool();
        }
        return instance;
    }  

 

对象池

 

思路:重复创建大量物理和销毁物体,会大量消耗资源,比如:子弹,金币等等,对象池的作用就是创建完了不销毁,只能把它隐藏存入对象池,用一个列表保存数据,需要用的时候再取出来,同时激活它,并且移除列表,这样列表中剩下的就是隐藏可以使用的对象。因为可能有很多物体需要用对象池,所以把个物体的LIST列表存入一个字典。如果对象池中有100个对象,创建90个都是在对象池中激活,只有创建110,才会再新生成10个。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//U3D单例对象池
public  class ObjectPool : MonoBehaviour
{
    public static ObjectPool instance;   //单例
    public GameObject[] prefabObjects;   //prefab数组
    private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();  //对象池字典
    //U3D的单例机制,是第一个挂载脚本的对象,就是该单例,后面再怎么重复挂载是无效的。因为挂载的时候就实例化instance了。
    void Awake()
    {
        instance = this; 
    }
    //从池中获取      //正式项目最好用一个静态类来保存名字,防止出错
    public GameObject GetOut(string GameObjectName, Vector3 vector3)
    {
        GameObject gameObject;   //返回的gameObject
        //如果池中有
        if (pool.ContainsKey(GameObjectName) && pool[GameObjectName].Count > 0)
        {
            //取池里的用
            gameObject = pool[GameObjectName][0];
            gameObject.SetActive(true);
            gameObject.transform.position = vector3;
            //取完移除
            pool[GameObjectName].RemoveAt(0);
        }
       //如果没有
        else
        {
            GameObject prefabObject = null;
            //要生成的prefabObject   prefab数组中的物体名字要和传入的字符串一致  
            for (int i = 0; i < prefabObjects.Length; i++)
            {
                if (prefabObjects[i].name == GameObjectName)
                {
                    prefabObject = prefabObjects[i];
                }
            }
            //直接创建
            gameObject = (GameObject)GameObject.Instantiate(prefabObject, vector3, Quaternion.identity);
        }
        return gameObject;
    }
    //存入对象池
    public void SetIn(string GameObjectName,GameObject gameObject) 
    {
      //池中没有
      if (!pool.ContainsKey(GameObjectName))
      {   //新建池List
          pool.Add(GameObjectName, new List<GameObject>());
      }
      //存入池
      gameObject.SetActive(false);
      pool[GameObjectName].Add(gameObject);
      Debug.Log(pool[GameObjectName].Count);
    }
    //销毁对象池
    public void DestroyPool(string GameObjectName)
    {
        if (pool.ContainsKey(GameObjectName))
        {   //删除对象
            for (int i = 0; i< pool[GameObjectName].Count; i++) 
            {
                Destroy(pool[GameObjectName][i]);
            }
            //移除列表
            pool.Remove(GameObjectName);
        }
    }
} 

注意:使用的时候,从对象池获取,什么地方都可以用,存入对象池这个方法最好在物体本身上,比如子弹上挂着一个,自己就把自身存入对象池。实际使用就用PoolManager插件好了,据说很好用

2017-03-06 19:22:15 qq_36215025 阅读数 398

为什么要使用单例模式

在我们的整个游戏生命周期当中,有很多对象从始至终有且只有一个。这个唯一的实例只需要生成一次,并且直到游戏结束才需要销毁。 
单例模式一般应用于管理器类,或者是一些需要持久化存在的对象。

Unity3d中单例模式的实现方式

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

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

以下代码来自于MSDN。

  1. public sealed class Singleton   
  2. {   
  3.    private static volatile Singleton instance;   
  4.    private static object syncRoot = new Object();   
  5.    public static Singleton Instance   
  6.    {   
  7.       get    
  8.       {   
  9.          if (instance == null)    
  10.          {   
  11.             lock (syncRoot)    
  12.             {   
  13.                if (instance == null)    
  14.                   instance = new Singleton();   
  15.             }   
  16.          }   
  17.          return instance;   
  18.       }   
  19.    }   
  20. }   
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的处理。同时,为了保持场景当中只有一个实例,我们要对当前场景中的单例进行判断,如果存在其他的实例,则应该将其全部删除。 

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

  1. public sealed class SingletonMoBehaviour: MonoBehaviour  
  2. {   
  3.     private static volatile SingletonBehaviour instance;   
  4.     private static object syncRoot = new Object();   
  5.     public static SingletonBehaviour Instance   
  6.     {   
  7.         get    
  8.         {   
  9.             if (instance == null)    
  10.             {   
  11.                 lock (syncRoot)    
  12.                 {   
  13.                     if (instance == null)  {  
  14.                         SingletonBehaviour[] instances = FindObjectsOfType<SingletonBehaviour>();  
  15.                         if (instances != null){  
  16.                             for (var i = 0; i < instances.Length; i++) {  
  17.                                 Destroy(instances[i].gameObject);  
  18.                             }  
  19.                         }  
  20.                         GameObject go = new GameObject(“_SingletonBehaviour”);  
  21.                         instance = go.AddComponent<SingletonBehaviour>();  
  22.                         DontDestroyOnLoad(go);   
  23.                     }  
  24.                 }   
  25.             }   
  26.             return instance;   
  27.         }   
  28.     }   
  29. }   
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的。 
代码

  1. public sealed class SingletonTemplate<T> : MonoBehaviour where T : MonoBehaviour  
  2. {  
  3.     private static volatile T instance;  
  4.     private static object syncRoot = new Object();  
  5.     public static T Instance  
  6.     {  
  7.         get  
  8.         {  
  9.             if (instance == null)  
  10.             {  
  11.                 lock (syncRoot)  
  12.                 {  
  13.                     if (instance == null)  
  14.                     {  
  15.                         T[] instances = FindObjectsOfType<T>();  
  16.                         if (instances != null)  
  17.                         {  
  18.                             for (var i = 0; i < instances.Length; i++)  
  19.                             {  
  20.                                 Destroy(instances[i].gameObject);  
  21.                             }  
  22.                         }  
  23.                         GameObject go = new GameObject();  
  24.                         go.name = typeof(T).Name;  
  25.                         instance = go.AddComponent<T>();  
  26.                         DontDestroyOnLoad(go);  
  27.                     }  
  28.                 }  
  29.             }  
  30.             return instance;  
  31.         }  
  32.     }  
  33. }  
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函数中调用单例对象,这可能导致该对象在游戏结束后依然存在(原本的单例类已经销毁了,你又创建了一个新的,当然就不会再销毁一次了)。举例来说,以下的代码是需要注意的的。
  1. void Start(){  
  2.     Singleton.Instance.OnSomeTime += DoSth;  
  3. }  
  4.   
  5. void OnDestroy(){  
  6.     Singleton.Instance.OnSomeTime -= DoSth;  
  7. }  
void Start(){
    Singleton.Instance.OnSomeTime += DoSth;
}

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


  • 此外,建议不要在场景或者预置当中放置拥有单例类组件的Gameobject。很多网上的项目有这样的写法。但我的观点是这种写法不够灵活。如果使用这种方法,注意在获取instance时,将找到的第一个对象赋给instance
  1. public static T Instance  
  2. {  
  3.     get  
  4.     {  
  5.         if (instance == null)  
  6.         {  
  7.             T[] instances = FindObjectsOfType<T>();  
  8.             if (instances != null)  
  9.             {  
  10.                 instance = instances[0];  
  11.                 for (var i = 1; i < instances.Length; i++)  
  12.                 {  
  13.                     Destroy(instances[i].gameObject);  
  14.                 }  
  15.             }  
  16.         }  
  17.         return instance;  
  18.     }  
  19. }  
    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类,等等。这里就不再扩展了。 

  1. using UnityEngine;  
  2.   
  3. public class GameRoot : MonoBehaviour {  
  4.   
  5.     //数据读取管理类  
  6.     [HideInInspector]  
  7.     public DBManager       dbManager;  
  8.   
  9.     //页面管理器  
  10.     [HideInInspector]  
  11.     public PageManager      pageManager;  
  12.   
  13.     private static object _lock = new object();  
  14.     private static GameRoot _instance;  
  15.     public static GameRoot Instance  
  16.     {  
  17.         get  
  18.         {  
  19.             lock (_lock)  
  20.             {  
  21.                 if (_instance == null)  
  22.                 {  
  23.                     GameObject go = new GameObject(“GameRoot”);  
  24.                     _instance = go.AddComponent<GameRoot>();  
  25.                 }  
  26.             }  
  27.             return _instance;  
  28.         }  
  29.     }  
  30.     private void Awake()  
  31.     {  
  32.         if (_instance == null)  
  33.         {  
  34.             _instance = this;  
  35.             _instance.Initialize();  
  36.         }  
  37.         else  
  38.         {  
  39.             Destroy(this);  
  40.             _instance = null;  
  41.         }  
  42.         DontDestroyOnLoad(this);  
  43.     }  
  44.   
  45.     void Initialize()  
  46.     {  
  47.         dbManager = gameObject.AddComponent<SqlManager>();  
  48.         dbManager.Init();  
  49.   
  50.         pageManager = gameObject.AddComponent<PageManager>();  
  51.         pageManager.Init();  
  52.     }  
  53. }  
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单例类:


  1. public class DBManager : MonoBehaviour {  
  2.   
  3.     private static DBManager _instance = null;  
  4.     public static DBManager Instance  
  5.     {  
  6.         get  
  7.         {  
  8.             if (_instance == null)  
  9.             {  
  10.                 _instance = GameRoot.Instance.dbManager;  
  11.             }  
  12.             return _instance;  
  13.         }  
  14.     }  
  15. }  
public class DBManager : MonoBehaviour {

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


没有更多推荐了,返回首页