Unity3d与设计模式(二)单例模式

为什么要使用单例模式

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

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

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-29 23:17:41

Unity3d与设计模式(二)单例模式的相关文章

一天一个设计模式(二) -单例模式(Singleton)

前言 单例模式 (Singleton) 是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点. 正文 (一). 优缺点 Java中单例模式 (Singleton) 是一种广泛使用的设计模式.单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在.一些管理器和控制器常被设计成单例模式. 1. 优点 提供了对唯一实例的受控访问. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象

设计模式二 单例模式

0.基本定义 单例模式,保证一个类仅有一个实例,并提供一个访问它但全局访问点. 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象.一个最好的办法,让类自身负责保存它的唯一实例. 这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法. 要求: 1.构造私有方法 2.定义一个实例,自己实例化 3.获取实例 spring 中默认bean加载都是单例. 1.实现方式 1.1.饿汉式 public class Emperor { private static

设计模式二:单例模式

一.什么是单例模式 单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问. 二.单例模式要点 以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象. 三.单例模式的类图 四.代码 有三种形式的代码,根据具体的情况选择: <一>懒汉模式 注:懒汉模式,要在第一次被引用的时候,才进行实例的初始化:但是,是线程不安全的: class Singleton{ private Singleto

Java设计模式:单例模式

概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能

[转]JAVA设计模式之单例模式

原文地址:http://blog.csdn.net/jason0539/article/details/23297037 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话

(九)JAVA设计模式之单例模式

JAVA设计模式之单例模式 一.单例模式的介绍 Singleton是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点.     全局对象和Singleton模式有本质的区别,因为大量使用全局对象会使得程序质量降低,而且有些编程语言根本不支持全局变量.最重要的是传统的全局对象并不能阻止一个类被实例化多次. 二.单例模式的特点 单例类只能有一个实例 单例类必须自己创建自己的唯一实例. 单例类必须给所有其他对象提供这一实例.

C#设计模式(1)——单例模式

一.引言 最近在设计模式的一些内容,主要的参考书籍是<Head First 设计模式>,同时在学习过程中也查看了很多博客园中关于设计模式的一些文章的,在这里记录下我的一些学习笔记,一是为了帮助我更深入地理解设计模式,二同时可以给一些初学设计模式的朋友一些参考.首先我介绍的是设计模式中比较简单的一个模式——单例模式(因为这里只牵涉到一个类) 二.单例模式的介绍 说到单例模式,大家第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只

设计模式(4)单例模式

概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能

JAVA设计模式之单例模式(转)

本文继续介绍23种设计模式系列之单例模式. 概念: java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器

文成小盆友python-num8 面向对象中的成员,成员修饰符,特殊成员,异常处理,设计模式之单例模式

本节主要内容: 1.面向对象中的成员 2.成员修饰符 3.特殊成员 4.异常处理 5.设计模式之单例模式 一.面向对象中的成员(类的成员) 类的成员总共可以分为3大类,每类中有不同的分支. 1.总述,基本分类 如下图所示: 类成员包括字段,方法,和属性 2.字段 如上图字段分为普通字段和静态字段,两者的使用有区别,但是最大的区别在于两者在内存中的保存位置有区别. 普通字段属于对象而静态字段属于类,在使用过程中谁的字段就由谁来调用. 静态字段和普通字段的定义如下: 在调用时分各自调用 #####类