举例讲解C#编程中对设计模式中的单例模式的运用
作者:LearningHard 发布时间:2023-04-28 19:34:10
单例模式的介绍
说到单例模式,大家第一反应应该就是——什么是单例模式?,从“单例”字面意思上理解为——一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了,下面给出单例模式的一个官方定义:确保一个类只有一个实例,并提供一个全局访问点。为了帮助大家更好地理解单例模式,大家可以结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路:
为什么会有单例模式
看完单例模式的介绍,自然大家都会有这样一个疑问——为什么要有单例模式的?它在什么情况下使用的?从单例模式的定义中我们可以看出——单例模式的使用自然是当我们的系统中某个对象只需要一个实例的情况,例如:操作系统中只能有一个任务管理器,操作文件时,同一时间内只允许一个实例对其操作等,既然现实生活中有这样的应用场景,自然在软件设计领域必须有这样的解决方案了(因为软件设计也是现实生活中的抽象),所以也就有了单例模式了。
剖析单例模式的实现思路
了解完了一些关于单例模式的基本概念之后,下面就为大家剖析单例模式的实现思路的,因为在我自己学习单例模式的时候,咋一看单例模式的实现代码确实很简单,也很容易看懂,但是我还是觉得它很陌生(这个可能是看的少的,或者自己在写代码中也用的少的缘故),而且心里总会这样一个疑问——为什么前人会这样去实现单例模式的呢?他们是如何思考的呢?后面经过自己的琢磨也就慢慢理清楚单例模式的实现思路了,并且此时也不再觉得单例模式模式的,下面就分享我的一个剖析过程的:
我们从单例模式的概念(确保一个类只有一个实例,并提供一个访问它的全局访问点)入手,可以把概念进行拆分为两部分:(1)确保一个类只有一个实例;(2)提供一个访问它的全局访问点;下面通过采用两人对话的方式来帮助大家更快掌握分析思路:
菜鸟:怎样确保一个类只有一个实例了?
老鸟:那就让我帮你分析下,你创建类的实例会想到用什么方式来创建的呢?
新手:用new关键字啊,只要new下就创建了该类的一个实例了,之后就可以使用该类的一些属性和实例方法了
老鸟:那你想过为什么可以使用new关键字来创建类的实例吗?
菜鸟:这个还有条件的吗?........., 哦,我想起来了,如果类定义私有的构造函数就不能在外界通过new创建实例了(注:有些初学者就会问,有时候我并没有在类中定义构造函数为什么也可以使用new来创建对象,那是因为编译器在背后做了手脚了,当编译器看到我们类中没有定义构造函数,此时编译器会帮我们生成一个公有的无参构造函数)
老鸟:不错,回答的很对,这样你的疑惑就得到解答了啊
菜鸟:那我要在哪里创建类的实例了?
老鸟:你傻啊,当然是在类里面创建了(注:这样定义私有构造函数就是上面的一个思考过程的,要创建实例,自然就要有一个变量来保存该实例把,所以就有了私有变量的声明,但是实现中是定义静态私有变量,朋友们有没有想过——这里为什么定义为静态的呢?对于这个疑问的解释为:每个线程都有自己的线程栈,定义为静态主要是为了在多线程确保类有一个实例)
菜鸟:哦,现在完全明白了,但是我还有另一个疑问——现在类实例创建在类内部,那外界如何获得该的一个实例来使用它了?
老鸟:这个,你可以定义一个公有方法或者属性来把该类的实例公开出去了(注:这样就有了公有方法的定义了,该方法就是提供方法问类的全局访问点)
通过上面的分析,相信大家也就很容易写出单例模式的实现代码了,下面就看看具体的实现代码(看完之后你会惊讶道:真是这样的!):
下面是Singleton.cs的内容:
using System;
using System.Collections;
using System.Collections.Generic;
public class Singleton : MonoBehaviour
{
private static GameObject m_Container = null;
private static string m_Name = "Singleton";
private static Dictionary<string, object> m_SingletonMap = new Dictionary<string, object>();
private static bool m_IsDestroying = false;
public static bool IsDestroying
{
get { return m_IsDestroying; }
}
public static bool IsCreatedInstance(string Name)
{
if(m_Container == null)
{
return false;
}
if (m_SingletonMap!=null && m_SingletonMap.ContainsKey(Name))
{
return true;
}
return false;
}
public static object getInstance (string Name)
{
if(m_Container == null)
{
Debug.Log("Create Singleton.");
m_Container = new GameObject ();
m_Container.name = m_Name;
m_Container.AddComponent (typeof(Singleton));
}
if (!m_SingletonMap.ContainsKey(Name)) {
if(System.Type.GetType(Name) != null)
{
m_SingletonMap.Add(Name, m_Container.AddComponent (System.Type.GetType(Name)));
}
else
{
Debug.LogWarning("Singleton Type ERROR! (" + Name + ")");
}
}
return m_SingletonMap[Name];
}
public void RemoveInstance(string Name)
{
if (m_Container != null && m_SingletonMap.ContainsKey(Name))
{
UnityEngine.Object.Destroy((UnityEngine.Object)(m_SingletonMap[Name]));
m_SingletonMap.Remove(Name);
Debug.LogWarning("Singleton REMOVE! (" + Name + ")");
}
}
void Awake ()
{
Debug.Log("Awake Singleton.");
DontDestroyOnLoad (gameObject);
}
void Start()
{
Debug.Log("Start Singleton.");
}
void Update()
{
}
void OnApplicationQuit()
{
Debug.Log("Destroy Singleton");
if(m_Container != null)
{
GameObject.Destroy(m_Container);
m_Container = null;
m_IsDestroying = true;
}
}
}
代码大部分都比较容易看懂,下面介绍几点注意的地方:
当我们在其他代码里需要访问某个单例时,只需调用getInstance函数即可,参数是需要访问的脚本的名字。我们来看一下这个函数。它首先判断所有单例所在的容器m_Container是否为空(实际上就是场景中是否存在一个Gameobject,上面捆绑了一个Singleton脚本),如果为空,它将自动创建一个对象,然后以“Singleton”命名,再捆绑Singleton脚本。m_SingletonMap是负责维护所有单例的映射。当第一次访问某个单例时,它会自动向m_Container上添加一个该单例类型的Component,并保存在单例映射中,再返回这个单例。因此,我们可以看出,单例的创建完全都是自动的,你完全不需要考虑在哪里、在什么时候捆绑脚本,这是多么令人高兴得事情!
在Awake函数中,有一句代码DontDestroyOnLoad (gameObject);,这是非常重要的,这句话意味着,当我们的场景发生变化时,单例模式将不受任何影响。除此之外,我们还要注意到,这句话也必须放到Awake函数,而不能放到Start函数中,这是由两个函数的执行顺序决定的,如果反过来,便可能会造成访问单例不成功,下面的例子里会更详细的介绍;
在OnApplicationQuit函数中,我们将销毁单例模式。
最后一点很重要:一定不要在OnDestroy函数中直接访问单例模式!这样很有可能会造成单例无法销毁。这是因为,当程序退出准备销毁单例模式时,我们在其他脚本的OnDestroy函数中再次请求访问它,这样将重新构造一个新的单例而不会被销毁(因为之前已经销毁过一次了)。如果一定要访问的话,一定要先调用IsCreatedInstance,判断该单例是否存在。
.NET实现单例模式的类
理解完了单例模式之后,菜鸟又接着问了:.NET FrameWork类库中有没有单例模式的实现呢?
经过查看,.NET类库中确实存在单例模式的实现类,不过该类不是公开的,下面就具体看看该类的一个实现的(该类具体存在于System.dll程序集,命名空间为System,大家可以用反射工具Reflector去查看源码的):
// 该类不是一个公开类
// 但是该类的实现应用了单例模式
internal sealed class SR
{
private static SR loader;
internal SR()
{
}
// 主要是因为该类不是公有,所以这个全部访问点也定义为私有的了
// 但是思想还是用到了单例模式的思想的
private static SR GetLoader()
{
if (loader == null)
{
SR sr = new SR();
Interlocked.CompareExchange<SR>(ref loader, sr, null);
}
return loader;
}
// 这个公有方法中调用了GetLoader方法的
public static object GetObject(string name)
{
SR loader = GetLoader();
if (loader == null)
{
return null;
}
return loader.resources.GetObject(name, Culture);
}
}
总结
到这里,设计模式的单例模式就介绍完了,希望通过本文章大家可以对单例模式有一个更深的理解,并且希望之前没接触过单例模式或觉得单例模式陌生的朋友看完之后会惊叹:原来如此!
猜你喜欢
- 本文实例讲述了C#实现利用泛型将DataSet转为Model的方法。分享给大家供大家参考。具体如下:因为网站需要用C#开发,习惯了java的
- Logback日志基础配置logback日志配置有很多介绍,但是有几个非常基础的,容易忽略的。下面是最简单的一个配置,注意加粗的描述<
- 一、问题Spring2.1.5集成activiti7.1.24时访问要输入用户名和密码。 @Autowired private
- 通用配置#下面介绍的整合JDBC和整合MyBatis都需要添加的实体类和配置数据库表#CREATE TABLE `user` ( `id`
- 生活中随处可见并行的例子,并行 顾名思义就是一起进行的意思,同样的程序在某些时候也需要并行来提高效率,在上一篇文章中我们了解了 Java 语
- 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异
- 栈栈(stack)又名堆栈,它是一种运算受限的线性表 。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
- Android onClick 与 setOnClickListener区别为Android Widgets添加点击事件处理函数又
- java 数据类型:在Java中,数据类型分为两大种:基本数据类型(值类型)和包装类型(引用数据类型)。基本数据类型不是对象,不能调用toS
- 介绍MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。比如执行前、执行后或者对SQL结果集处理、sql入参处理等,这样就可以在
- 1、profiles是什么?有什么作用在maven构建的项目都存在一个pom.xml的项目对象模型配置文件,用于约束项目(如:jar包管理、
- 本文实例讲述了java生成xml格式文件的方法。分享给大家供大家参考,具体如下:这里演示利用Java生成xml格式文件Demo中所用到的ja
- 目录int和Integer的区别及自动装箱和自动拆箱Integer和int的对比,如下所示:自动装箱和自动拆箱:Integer的自动拆装箱的
- Java栈之链式栈存储结构实现一、链栈采用单链表来保存栈中所有元素,这种链式结构的栈称为链栈。二、栈的链式存储结构实现package com
- 一、SpringBoot 指定配置文件路径:在 SpringBoot 中,可以将配置文件放在 jar 包外面,这样可以方便地修改配置而不需要
- 一、题目描述题目实现:在进行网络编程时,由于进行网络连接是比较消耗资源的,因此,可以对连接的等待时间进行设置,如果在规定的时间没有进行连接,
- 本文实例讲述了Android6.0开发中屏幕旋转原理与流程。分享给大家供大家参考,具体如下:从Android 系统开发开始,这里写下Andr
- springboot 多个filter的执行顺序以及配置当项目中有多个filter时,为了便于管理,可以创建一个配置文件,对所有的filte
- 计数排序是非比较的排序算法,用辅助数组对数组中出现的数字计数,元素转下标,下标转元素计数排序优缺点优点:快缺点:数据范围很大,比较稀疏,会导
- BigDecimal的舍入模式(RoundingMode)BigDecimal.divide方法中必须设置roundingMode,不然会报