游戏设计模式----单例模式

定义

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

C#

单例模式主要分两种

饿汉式

在类加载时已经创建好该单例对象。

public class Test_Instan
{

    private static Test_Instan instant=new Test_Instan();
    public static Test_Instan Instant
    {
        get
        {
           return instant;
        }
     }  
private Test_Instan()
    {

    }
}
懒汉式

在真正需要使用对象时才去创建该单例类对象

public class Test_Instan
{
    private static Test_Instan instant;
    public static Test_Instan Instant
    {
        get
        {
            if (instant==null)
            {
                instant = new Test_Instan();
            }
           return instant;
        }
}  

private Test_Instan()
    {

 }

}

不足之处:如果两个线程同时判断instant为空那么它们都会去实例化一个Test_Instan对象,这就变成双例了。

改进:利用锁

public class Test_Instan
{	private Test_Instan()
    {

 	}
    private static Test_Instan instant;
    private static object syncRoot = new object();
    public static Test_Instan Instant
    {
        get
        {
            if (instant == null) // 假如线程A和线程B同时看到instant为null
            {
                lock (syncRoot)//线程A或者线程B获得该锁进行初始化,另一线程阻塞
                {
                    if (instant == null)//其中一个线程进入该分支创建单例对象,另外一个阻塞线程,阻塞结束后进入该分支则会发现instant已被创建
                    {
                        instant = new Test_Instan();
                    }
                }
            }
                   
           return instant;
        }
    }  

}

加双锁

public class Test_Instan
{	private Test_Instan()
    {

 	}
    private volatile static Test_Instan instant;
    private static object syncRoot = new object();
    public static Test_Instan Instant
    {
        get
        {
            if (instant == null) // 假如线程A和线程B同时看到instant为null
            {
                lock (syncRoot)//线程A或者线程B获得该锁进行初始化,另一线程阻塞
                {
                    if (instant == null)//其中一个线程进入该分支创建单例对象,另外一个阻塞线程,阻塞结束后进入该分支则会发现instant已被创建
                    {
                        instant = new Test_Instan();
                    }
                }
            }
                   
           return instant;
        }
    }  

}

为什么加双锁

   if (instance == null) {
            instance = new Singleton();//erro
        }

如果不使用volatile关键字,隐患来自于上述代码中注释了 erro 的一行,这行代码大致有以下三个步骤:

在堆中开辟对象所需空间,分配地址
根据类加载的初始化顺序进行初始化
将内存地址返回给栈中的引用变量

由于编译器允许处理器乱序执行,所以第二步和第三步的顺序无法保证。如果第三步先执行完毕、第二步未执行时,有另外的线程调用了instance,由于已经赋值,将判断不为null,拿去直接使用,但其实构造函数还未执行,成员变量等字段都未初始化,直接使用,就会报错。

而对volatile变量的写操作,不允许和它之前的读写操作打乱顺序;对volatile变量的读操作,不允许和它之后的读写乱序。

当一个线程要使用共享内存中的volatile变量时,它会直接从主内存中读取,而不是使用自己本地内存中的副本。当一个线程对一个volatile变量进行写时,它会将这个共享变量值刷新到共享内存中。

Unity的单例
饿汉式

类似与我们上面的饿汉式,不过这里要注意的是Awake函数的执行顺序是不可控的,通俗来说每次运行程序的时候每个脚本的执行顺序都不一样,通常我们会自己写一个初始函数函数,来控制我们的执行循序,确保单例的初始化在所有的逻辑之上。

public class SingletonUnity : MonoBehaviour
{

    private static SingletonUnity instant;
    public static SingletonUnity Instant
    {
        get
        {
            return instant;
        }
    }
    private void Awake()
    {
        instant = this;
    }
}
懒汉式

由于unity的所有继承自MonoBehaviour的脚本都必须挂在一个游戏对象上,否则无法执行,也谈不上用new来实例化,这点我们要尤为注意。

public class SingletonUnity : MonoBehaviour
{
    private static SingletonUnity instant;

    public static SingletonUnity Instant
    {
        get
        {
            if (instant==null)
            {
                instant= FindObjectOfType<SingletonUnity>();
                if (instant==null)
                {
                    GameObject instan_ = new GameObject();
                    instant = instan_.AddComponent<SingletonUnity>();
                }
            }
            return instant;
        }
    }
}
问题

对比我们上面纯C#的写法,在Unity创建的MonoBehaviour类的单例并没有对MonoBehaviour类的实例化进行非公有化,因为,在Unity中,MonoBehaviour类有可视化操作的特点,当我们手动拖拽多个单例脚本到游戏中,这时我们运行程序很可能会出现逻辑错误,我们单例模式的原则或者说目的是保证一个类只有一个实例,所以我们需要改进一下。

public class SingletonUnity : MonoBehaviour
{
    private static SingletonUnity instant;

    public static SingletonUnity Instant
    {
        get
        {
            if (instant == null)
            {
                SingletonUnity[] instants = FindObjectsOfType<SingletonUnity>();                 
                  for (int i = 0; i < instants.Length; i++)
                  {
                    Destroy(instants[i].gameObject);
                  }

                GameObject instan_ = new GameObject();
                instant = instan_.AddComponent<SingletonUnity>();
            }
            return instant;
        }
    }

   
}

最后写个测试脚本测试下

public class Client_sigle : MonoBehaviour {


    void Start()
    {
        Debug.Log(SingletonUnity.Instant.name);
       
    }
  

}

运行前

在这里插入图片描述
在这里插入图片描述

运行后
在这里插入图片描述

当然,你也可以按自己的需求,更改上面写法。

改进:上面单例的写法代码量比较多,当游戏中有多个单例,显然不可能用我们的复制粘贴,这时我们可以用泛型来增强我们的复用性。

public class SingletonUnity<T> : MonoBehaviour where T: MonoBehaviour
{
    private static T instant;

    public static T Instant
    {
        get
        {
            if (instant == null)
            {
                T[] instants = FindObjectsOfType<T>();                 
                  for (int i = 0; i < instants.Length; i++)
                  {
                    Destroy(instants[i].gameObject);
                  }

                GameObject instan_ = new GameObject();
                instant = instan_.AddComponent<T>();
            }
            return instant;
        }
    }

}

只需要让单例继承该类即可。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值