首页 > 代码库 > C#设计模式-单例模式

C#设计模式-单例模式

前言

最近开始花点心思研究下设计模式,主要还是让自己写的代码可重用性高、保证代码可靠性。所谓设计模式,我找了下定义:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

为什么要提倡“Design Pattern(设计模式)”?

根本原因是为了代码复用,增加可维护性。因此这次我们来学习下设计模式,最后会通过C#语言来实现这些设计模式作为例子,深刻理解其中的精髓。

定义

单例模式,估计是最简单的一种设计模式了吧。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源(确保一个类只有一个实例,并提供一个全局访问点)。

特点

  • 1。单例模式的类只提供私有的构造函数
  • 2。类定义中含有一个该类的静态私有对象
  • 3。该类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象

 

优缺点

优点:

一、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点:

一、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。

实现思路

从上面的定义中可以看出,一个单例类要有一个静态的属性来保存它唯一的实例 ,需要将类的构造方法设置为private,不能在外界通过new创建实例,这样你不允许其他任何类来创建单例类的实例,因为它们不能访问单例类的构造方法。 但是因为其他类不能实例化单例类,那么我们如何使用它呢?答案就是单例类需要提供服务来对外提供类的实例,可以定义一个公有方法或者属性来把该类的实例公开出去了。

         一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例

思路明白后,便实现这个Singleton类。

namespace DesignPattern{    /// <summary>    /// 单例模式    /// </summary>    public class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton mySingleton;        // 定义私有构造函数,使外界不能创建该类实例        private Singleton()        {        }        //定义公有方法提供一个全局访问点。        public static Singleton GetInstance()        {            // 如果类的实例不存在则创建,否则直接返回            if (mySingleton == null)            {                mySingleton = new Singleton();            }            return mySingleton;        }    }}

这个Singleton单例模式类实现到这里,我貌似在之前做的asp.net MVC项目中得到了共鸣,原来之前在asp.net的网站中,我用来实现http上下文,工具类等等东西的时候,也用了类似的方法,保证了实例化的时候只有一个实例,从而减少开销。

/// <summary>    /// 单例模式    /// </summary>    public class OperateContext    {        #region 获取当前操作上下文(存在线程中,提高效率) + OperateContext Current        // <summary>        /// 获取当前操作上下文(存在线程中,提高效率)        /// </summary>        public static OperateContext Current        {            get            {                OperateContext o = CallContext.GetData(typeof(OperateContext).Name) as OperateContext;                if (o == null)                {                    o = new OperateContext();                    CallContext.SetData(typeof(OperateContext).Name, o);                }                return o;            }        }        #endregion        #region Http上下文 以及相关属性         /// <summary>        /// Http上下文        /// </summary>        HttpContext ContextHttp        {            get            {                return HttpContext.Current;            }        }        HttpResponse Response        {            get            {                return ContextHttp.Response;            }        }        HttpRequest Request        {            get            {                return ContextHttp.Request;            }        }        HttpSessionState Session        {            get            {                return ContextHttp.Session;            }        }        #endregion    }

贴了下之前项目中的一部分代码,其中里面还有很多工具类之类访问比较频繁的类,于是乎便通过这种方式来提高这个效率,之前做这个项目的时候,这个提高线程效率的方法仅仅是因为需要而学习来的一套做法,原来这个便是设计模式中的一种。

然而这个看似完美的单例其实有那么一个缺陷,其实这个缺陷在我做网络爬虫的时候就有遇到过,而且也去解决过,对,你没有猜错,便是多线程的问题。也就是如果出现多个线程同时访问这个方法的时候,就都会创建多个实例,这样就违背了我们单例模式初衷了,这个时候通过Lock自定义的锁来确保一个时间内只允许一个线程来访问便可以。

namespace DesignPattern{    /// <summary>    /// 单例模式    /// </summary>    public class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton mySingleton;        // 定义一个标识确保线程同步        private static readonly object locker = new object();        // 定义私有构造函数,使外界不能创建该类实例        private Singleton()        {        }        //定义公有方法提供一个全局访问点。        public static Singleton GetInstance()        {            //这里的lock其实使用的原理可以用一个词语来概括“互斥”这个概念也是操作系统的精髓            //其实就是当一个进程进来访问的时候,其他进程便先挂起状态            lock (locker)            {                // 如果类的实例不存在则创建,否则直接返回                if (mySingleton == null)                {                    mySingleton = new Singleton();                }            }            return mySingleton;        }    }}

        这里的lock其实使用的原理可以用一个词语来概括“互斥”这个概念也是操作系统的精髓,其实就是当一个进程进来访问的时候,其他进程便先挂起状态。说到互斥,它是指多线程存在时必须互斥访问的资源。也就是某一时刻不允许多个进程同时访问,只能单个进程的访问。我们把这些程序的片段称作临界区或临界段,它存在的目的是有效的防止竞争又能保证最大化使用共享数据。而这些并发进程必须有好的解决方案,才能防止出现以下情况:多个进程同时处于临界区,临界区外的进程阻塞其他的进程,有些进程在临界区外无休止的等待。

       上面代码对于每个线程都会对线程辅助对象locker加锁之后再判断实例是否存在,对于这个操作完全没有必要的,因为当第一个线程创建了该类的实例之后,此时完全没必要对线程辅助对象加锁之后再去判断,所以上面的实现方式增加了额外的开销,损失了性能,为了改进上面实现方式的缺陷,我们只需要在lock语句前面加一句(mySingleton==null)的判断就可以避免锁所增加的额外开销,这种实现方式我们就叫它 “双重锁定”

namespace DesignPattern{    /// <summary>    /// 单例模式    /// </summary>    public class Singleton    {        // 定义一个静态变量来保存类的实例        private static Singleton mySingleton;        // 定义一个标识确保线程同步        private static readonly object locker = new object();        // 定义私有构造函数,使外界不能创建该类实例        private Singleton()        {        }        //定义公有方法提供一个全局访问点。        public static Singleton GetInstance()        {            //这里的lock其实使用的原理可以用一个词语来概括“互斥”这个概念也是操作系统的精髓            //其实就是当一个进程进来访问的时候,其他进程便先挂起状态            if (mySingleton == null)//区别就在这里            {                lock (locker)                {                    // 如果类的实例不存在则创建,否则直接返回                    if (mySingleton == null)                    {                        mySingleton = new Singleton();                    }                }            }            return mySingleton;        }    }}

总结

      到这里,设计模式的单例模式就一起学习了,xmfdsh我也是第一次接触这个设计模式,因此会对逐渐对自己写的设计模式的博客进行修改和完善,毕竟没有深入去实践的时没有发言权的。从之前做的项目便可以看出,设计模式其实一直就在我们的身边,只是我们没有去发现,没有去总结,而设计模式这种东西也就是前任总结的一套代码设计经验的总结,因此学习设计模式比较重要,可以系统的去深入了解,

C#设计模式-单例模式