C#设计模式之一单例模式(Singleton Pattern)【建立型】

1、引言

     看了李建忠老师的讲的设计模式已经有一段时间了(这段时间大概有一年多了),本身尚未写过本身的、有关设计模式的文章。此次想写一些关于设计模式的文章,用本身的理解和代码来写,算是复习一遍。写做的过程当中也会多看看其余大牛的文章,争取让本身的理解正确,不然把你们带跑偏了,就是个人过错了。今天就开始咱们第一个设计模式,该模式是:【单例模式】,英文名称:Singleton Pattern,这个模式很简单,一个类型只须要一个实例,他是建立型的设计模式。为何叫“建立型”设计模式呢,由于他们有分类。固然了分类的方式不同,分类的结果也就不同。设计模式

    从目的来看:多线程

       -建立型(Creational)模式:负责对象建立函数

       -结构型(Structural)模式:处理类与对象间的组合性能

       -行为型(Behavioral)模式:类与对象交互中的职责分配学习

   从范围来看:优化

      -类模式处理类与子类的静态关系spa

      -对象模式处理对象间的动态关系线程

  以上就是分类的方式,咱们按大多数的分类,采用“从目的来看”的分类来对设计模式进行分类,咱们就开始今天的学习吧。

2、单例模式的介绍

   2.一、动机(Motivate)设计

          在软件系统中,常常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。code

     如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?

         这应该是类设计者的责任,而不是使用者的责任

   2.二、意图(Intent)

         保证一个类仅有一个实例,并提供一个该实例的全局访问点。                                   --《设计模式GoF》

   2.三、结构图(Structure)

      

  2.四、模式的组成

         (1)、单件实例(Singleton):这个模式里面只有一个类型,就是Singleton类型,而且这个类只有一个实例,能够经过Instance()方法获取该类型的实例。

  2.五、单件模式的代码实现

         既然是单实例,确定会涉及到多线程的问题,咱们就一步一步的来写代码,咱们先看看单线程Singleton模式的实现,代码以下:

 1 /// <summary>  2 /// 单例模式的实现  3 /// </summary>  4 public sealed class Singleton  5 {  6 // 定义一个静态变量来保存类的实例  7 private static Singleton uniqueInstance;  8  9 // 定义私有构造函数,使外界不能建立该类实例 10 private Singleton() 11  { 12  } 13 14 /// <summary> 15 /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点 16 /// </summary> 17 /// <returns></returns> 18 public static Singleton GetInstance() 19  { 20 // 若是类的实例不存在则建立,不然直接返回 21 if (uniqueInstance == null) 22  { 23 uniqueInstance = new Singleton(); 24  } 25 return uniqueInstance; 26  } 27 }


    私有的实例构造器是屏蔽外界的调用,上面的单例模式的实如今单线程下确实是完美的,也很好的知足了咱们单线程环境的需求。

    单线程单例模式的几个要点:

    (1)、Singleton模式中的实例构造器能够设置为protected以容许子类派生。

    (2)、Singleton模式通常不要支持ICloneable接口,由于这可能会致使多个对象实例,与Singleton模式的初衷违背。

    (3)、Singleton模式通常不要支持序列化,由于这也有可能致使多个对象实例,一样与Singleton模式的初衷违背。

    (4)、Singletom模式只考虑到了对象建立的工做,没有考虑对象销毁的工做。为何这样作呢,由于Net平台是支持垃圾回收的,因此咱们通常没有必要对其进行销毁处理。

    (5)、不能应对多线程环境:在多线程环境下,使用Singleton模式仍然有可能获得Singleton类的多个实例对象


    若是放在多线程环境下,问题就出来了。由于在两个线程同时运行GetInstance方法时,此时两个线程判断(uniqueInstance ==null)这个条件时都返回真,此时两个线程就都会建立Singleton的实例,这样就违背了咱们单例模式初衷了。要想解决这个问题,只要让GetInstance方法在同一时间只运行一个线程运行就行了,让咱们看看多线程Singleton模式的实现,代码以下:

 1     /// <summary>  2 /// 单例模式的实现  3 /// </summary>  4 public sealed class Singleton  5  {  6 // 定义一个静态变量来保存类的实例  7 private static volatile Singleton uniqueInstance;  8  9 // 定义一个标识确保线程同步 10 private static readonly object locker = new object(); 11 12 // 定义私有构造函数,使外界不能建立该类实例 13 private Singleton() 14  { 15  } 16 17 /// <summary> 18 /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22  { 23 // 当第一个线程运行到这里时,此时会对locker对象 "加锁", 24 // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 25 // lock语句运行完以后(即线程运行完以后)会对该对象"解锁" 26 lock (locker) 27  { 28 // 若是类的实例不存在则建立,不然直接返回 29 if (uniqueInstance == null) 30  { 31 uniqueInstance = new Singleton(); 32  } 33  } 34 35 return uniqueInstance; 36  } 37 }

    上面这种解决方案确实能够解决多线程的问题,可是上面代码对于每一个线程都会对线程辅助对象locker加锁以后再判断实例是否存在,对于这个操做彻底没有必要的,由于当第一个线程建立了该类的实例以后,后面的线程此时只须要直接判断(uniqueInstance==null)为假,此时彻底不必对线程辅助对象加锁以后再去判断,因此上面的实现方式增长了额外的开销,损失了性能,为了改进上面实现方式的缺陷,咱们只须要在lock语句前面加一句(uniqueInstance==null)的判断就能够避免锁所增长的额外开销,这种实现方式咱们就叫它 “双重锁定(Double Check)”,下面具体看看实现代码的:

 1     /// <summary>  2 /// 单例模式的实现  3 /// </summary>  4 public sealed class Singleton  5  {  6 // 定义一个静态变量来保存类的实例  7 private static volatile Singleton uniqueInstance;  8  9 // 定义一个标识确保线程同步 10 private static readonly object locker = new object(); 11 12 // 定义私有构造函数,使外界不能建立该类实例 13 private Singleton() 14  { 15  } 16 17 /// <summary> 18 /// 定义公有方法提供一个全局访问点,同时你也能够定义公有属性来提供全局访问点 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22  { 23 // 当第一个线程运行到这里时,此时会对locker对象 "加锁", 24 // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 25 // lock语句运行完以后(即线程运行完以后)会对该对象"解锁" 26 // 双重锁定只须要一句判断就能够了 27 if (uniqueInstance == null) 28  { 29 lock (locker) 30  { 31 // 若是类的实例不存在则建立,不然直接返回 32 if (uniqueInstance == null) 33  { 34 uniqueInstance = new Singleton(); 35  } 36  } 37  } 38 return uniqueInstance; 39  } 40 }

        volatile修饰:编译器在编译代码的时候会对代码的顺序进行微调,用volatile修饰保证了严格意义的顺序。一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都当心地从新读取这个变量的值,而不是使用保存在寄存器里的备份。

3、C#中实现了单例模式的类

    如今咱们看看,如何使用C#语言的特性来实现单例的Singleton模式。

//Singleton模式的实现 public sealed class Singleton { public static readonly Singleton instance=new Singleton(); private Singleton(){} } 以上是内联初始化(生成的同时进行初始化)的单例模式,它等同于: public sealed class Singleton { public static readonly Singleton instance; //静态构造函数,CLR只执行一次 static Singleton() { instance=new Singleton(); } //私有构造函数,防止外界调用 private Singleton(){} }

    内联初始化实际上是把静态的字段放到静态构造器去初始化。只要想访问静态字段,一定已经在以前执行了静态构造器。这样也可以精确地保证使用的时候必定能拿到实例,若是不使用也不会实例化对象,也就是延时加载的功能。他一样可以支持多线程环境,由于只可能有一个线程执行静态构造器,不可能有多个线程去执行静态构造器,感受就是程序已经自动为咱们加锁了。

     它的一点弊端就是它不支持参数化的实例化方法。在.NET里静态构造器只能声明一个,并且必须是无参数的,私有的。所以这种方式只适用于无参数的构造器。

     须要说明的是:HttpContext.Current就是一个单例,他们是经过Singleton的扩展方式实现的,他们的单例也并非覆盖全部领域,只是针对某些局部领域中,是单例的,不一样的领域中仍是会有不一样的实例。

4、Singleton模式的扩展

     (1)、将一个实例扩展到n个实例,例如对象池的实现。(n不是指无限个实例,而是固定的某个数)

     (2)、将new构造器的调用转移到其余类中,例如多个类协同工做环境中,某个局部环境只须要拥有某个类的一个实例。

     (3)、理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的实例构造器的任意调用”。

5、单例模式的实现要点

      一、Singleton模式是限制而不是改进类的建立。

      二、Singleton类中的实例构造器能够设置为Protected以容许子类派生。

      三、Singleton模式通常不要支持Icloneable接口,由于这可能致使多个对象实例,与Singleton模式的初衷违背。

      四、Singleton模式通常不要支持序列化,这也有可能致使多个对象实例,这也与Singleton模式的初衷违背。

      五、Singleton只考虑了对象建立的管理,没有考虑到销毁的管理,就支持垃圾回收的平台和对象的开销来说,咱们通常不必对其销毁进行特殊的管理。

      六、理解和扩展Singleton模式的核心是“如何控制用户使用new对一个类的构造器的任意调用”。

      七、能够很简单的修改一个Singleton,使它有少数几个实例,这样作是容许的并且是有意义的。

       1】、单例模式的优势:

             (1)、实例控制:Singleton 会阻止其余对象实例化其本身的 Singleton 对象的副本,从而确保全部对象都访问惟一实例

             (2)、灵活性:由于类控制了实例化过程,因此类能够更加灵活修改实例化过程

        2】、单例模式的缺点:

             (1)、开销:虽然数量不多,但若是每次对象请求引用时都要检查是否存在类的实例,将仍然须要一些开销。能够经过使用静态初始化解决此问题。

             (2)、可能的开发混淆:使用 singleton 对象(尤为在类库中定义的对象)时,开发人员必须记住本身不能使用 new 关键字实例化对象。由于可能没法访问库源代码,所以应用程序开发人员可能会意外发现本身没法直接实例化此类。

             (3)、对象的生存期:Singleton 不能解决删除单个对象的问题。由于它包含对该静态的私有字段的引用,静态字段是不能被CLR回收内存的,该实例会和应用程序生命周期同样长,一直存在。

       3】、单例模式的使用场合:

            (1)、当类只能有一个实例并且客户能够从一个众所周知的访问点访问它时。

            (2)、当这个惟一实例应该是经过子类化可扩展的,而且客户应该无需更改代码就能使用一个扩展的实例时。

6、总结    到这里,单例模式就介绍完了,这个模式很简单,理解起来也不是很难,只要把握住代码的实现技巧,通常问题都不大,可是要找好使用的时机,若是使用错误,一些逻辑错误比较难排查。

相关文章
相关标签/搜索