c#设计模式之单例模式
单例模式
前言:在自己的学习设计模式过程的当中,感觉单例模式是我们编程过程当中使用比较频繁和相对较简单的一种,写这篇博客主要是参考设计模式的书和自己的一些理解。
一:首先大家先看一个例子:我们大家在平常的上QQ和FeiQ的过程当中,有没有发现这样的一个问题,就是我们每次上QQ的时候可以同时的打开多个QQ,而FeiQ问什么我们如何点击在我们的电脑上只能有一个。这样的话我们是不是可以这样认为:当我们在打开QQ的时候可以同时的创建多个实例,而我们的FeiQ只能创建一个实例。
接下来我们想实现一个功能:当我点击一个按钮的时候,需要创建出来一个窗体,而且这个窗体只能创建一次。
这个问题就需要用到我们的单例模式的思想了。首先在这里先给出它的定义:保证一个类中仅有一个实例,并提供一个访问它的全局访问点。
在自己的理解的过程当中,个人认为想要实现一个单例模式主要分三个主要的步骤。
1.构造函数的私有化,目的:主要防止在别的类中通过new关键字进行实例化(防止出现多个实例)。
1 /// <summary> 2 /// 构造函数的私有化 3 /// </summary> 4 private Form2() 5 { 6 InitializeComponent(); 7 }
2.声明一个静态的字段来模拟全局变量。
//全局唯一的单例 public static Form2 FrmSingle = null;
3.创建一个静态的方法,创建自己的唯一的实例。
/// <summary> /// 创建一个静态的方法,主要负责创建自己的唯一的实例 /// </summary> /// <returns>返回字段</returns> public static Form2 GetSingle() { if (FrmSingle == null) { FrmSingle = new Form2(); } return FrmSingle; }
这样的话我们一个简单的单例模式就实现了,从而解决了上面的问题,完成的代码的实现如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace _2_单例模式 { public partial class Form2 : Form { //全局唯一的单例 public static Form2 FrmSingle = null; /// <summary> /// 构造函数的私有化 /// </summary> private Form2() { InitializeComponent(); } /// <summary> /// 创建一个静态的方法,主要负责创建自己的唯一的实例 /// </summary> /// <returns>返回字段</returns> public static Form2 GetSingle() { if (FrmSingle == null) { FrmSingle = new Form2(); } return FrmSingle; } } }
客户端代码的调用,代码如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace _2_单例模式 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 按钮的点击 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { //创建窗体对象 Form2 frm2 = Form2.GetSingle(); //窗体的显示 frm2.Show(); } } }
二:多线程下的单例。
问题:比如在多线程的程序当中,当同时进行访问Form2类的时候,当调用GetSingle()方法的时候,也会可能造成创建多个实例的。这个时候我们该如何解决呢??
解决方法:我们可以给进程加一把锁,在时候我们需要用到c#中的Lock关键字。在这里解释一下Lock的含义:Lock是确保当一个线程位于代码的临界区时,另一个线程比进入临界区。如果其他的线程试图进入锁定的代码时,则它将进行等待(则被阻止),直到该对象被释放才能进行访问。
接下来我们将演讲如何进行编码:
1.程序运行时创建一个静态只读的进程辅助对象。
//程序运行时创建一个静态只读的进程辅助对象 private static readonly object syncRoot = new object();
2.加锁的处理。
/// <summary> /// 创建一个静态的方法,主要负责创建自己的唯一的实例 /// </summary> /// <returns>返回字段</returns> public static Form2 GetSingle() { //在同一时刻加了锁的那部分程序只有一个线程可以进入. lock (syncRoot) { if (FrmSingle == null) { FrmSingle = new Form2(); } } return FrmSingle; }
由此这段代码使得对象是实例有最先进入的那个线程创建,以后的线程在进入时都不会在创建对象的实例了,由于有了Lock,就保证了在多线程的环境下的同时访问也不会在造成多个实例的生成了。
在这个时候有人会问:为什么不直接Lock(FrmSingle),而是要在创建一个syncRoot来进行Lock呢??
因为我们在加锁的时候FrmSingle有没有被创建实例都不知道,我们怎样对它加锁呀。这时候就有一个问题了:那我们每次调用GetSingle方法时都需要Lcok,是不是有些不合理呢?所以我们需要对这个方法在进行改造。
三:双重锁定
代码直接展示:
/// <summary> /// 创建一个静态的方法,主要负责创建自己的唯一的实例 /// </summary> /// <returns>返回字段</returns> public static Form2 GetSingle() { //在同一时刻加了锁的那部分程序只有一个线程可以进入. if (FrmSingle == null) { lock (syncRoot) { if (FrmSingle == null) { FrmSingle = new Form2(); } } } return FrmSingle; }
接下来看截图来思考一个问题:
问题:我们已经在外面判断了FrmSingle实例是否存在,为什么还要在Lock里面在在做一次FrmSingle实例是否存在的判断呢?
问题的解答:首先对于FrmSingle存在的情况,如果存在的话就直接返回,这个是没有任何的问题的。如果当我们的实例为空的情况下,如果此时有两个线程同时的访问GetSingle();方法的时候,他们都可以通过第一重FrmSingle == null的判断,这时由于Lock机制,这两个线程则只有一个进入,另一个则在外面进行排队等候,必须要其中的一个进入并且出来后,另一个才可以进入,如果此时没有第二重FrmSingle == null的判断,则第一个线程创建了实例,而第二个线程还是可以继续创建新的实例。如果有第二个FrmSingle == null的话,当第一个线程创建实例之后并且出来之后,当第二个在进行访问的时候,这时候FrmSingle已经不为空(或者说是创建了实例),就不会再执行创建新的实例的代码。这样的话就确保了我们当前的类中有且只有一个实例,这也正符合我们的单例模式。
完整的代码如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace _2_单例模式 { public partial class Form2 : Form { //全局唯一的单例 public static Form2 FrmSingle = null; //程序运行时创建一个静态只读的进程辅助对象 private static readonly object syncRoot = new object(); /// <summary> /// 构造函数的私有化 /// </summary> private Form2() { InitializeComponent(); } /// <summary> /// 创建一个静态的方法,主要负责创建自己的唯一的实例 /// </summary> /// <returns>返回字段</returns> public static Form2 GetSingle() { //在同一时刻加了锁的那部分程序只有一个线程可以进入. if (FrmSingle == null) { lock (syncRoot) { if (FrmSingle == null) { FrmSingle = new Form2(); } } } return FrmSingle; } } }
是不是感觉有点麻烦呢,接下来我们讲解一下c#与公共语言运行库提供的一种方法。
四:静态初始化
其实在c#与公共语言运行库提供的一种“静态初始化”方法,这种方法不需要开发人员显示的编写线程安全的代码,即可以解决多线程环境下它是不安全的问题。代码如下,谈不上更好,只是写法更加的简单,请看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 静态初始化方法 { //sealed:阻止发生派生,因为派生有可能会增加实例. public sealed class From { //在第一次引用类的任何成员时创建实例,公共语言运行库负责处理变量的初始化 private static readonly From _instance = new From(); /// <summary> /// 构造函数的私有化 /// </summary> private From() { } /// <summary> /// 静态的方法创建唯一的实例 /// </summary> /// <returns>返回字段</returns> public static From Instance() { return _instance; } } }
这种写法也就解决了两个基本的问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。不同之处在于它依赖于公共语言运行库来初始化变量。由于构造是私有的,所以不能再类的外面实例化From类,因此变量的引用在系统中存在唯一的实例,不过要注意的是,_instance的前面要加上关键字readonly,这意味着只能在静态初始化期间或在类的构造函数中分配变量。由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以别称为饿汉式单例类,原先的单例模式处理方式要在第一次被引用时,才会将自己实例化,所以被称之为懒汉式单例类。
所以使用哪种方式,取决于自己实际的需求,一般来说,饿汉式单例类已经能够满足我们的需求了。
好了,单例模式已经讲解完了,如果有什么问题大家可以积极的留言,共同进步。