专栏首页.Net、.Net Core 、Docker通俗易懂设计模式解析——单例模式

通俗易懂设计模式解析——单例模式

一、前言

  在上一节中我们对设计模式进行了一定的介绍及分类。设计模式分为创建型、结构型、行为型。创建型模式——主要负责对象的创建。结构型职责——主要负责处理类与对象的组合。行为型模式——主要负责类与对象交互中的职责的分配问题。今天我们也是讲述介绍创建型模式中的第一个模式——单例模式

二、单例模式介绍

  (一)来由

    单例模式(Singleton Pattern)是最简单的一个设计模式 ,这种设计模式属于创建型模式。在程序中总会有一些特殊的类。它们必须保证在系统中只存在一个实例,这个单一的类自己创建自己的对象,同时确保只有单个对象被创建,并且提供唯一的访问形式。可以直接进行访问,不用再新建实例。

    那么如何避开常规的设计,来实现一个类一个实例、并且保证唯一调用呢?这时候就是单例模式施展身手的时候了。

  (二)意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  (三)单例模式实现方法

    单例模式到底又是如何实现的呢?既然是单一实例,那么队友多线程又该如何处理呢?下面我们一一来看看单例模式的实现。单例模式我们又涉及到其实现的多种形式——非线程安全、线程安全、双重验证线程安全、不用锁线程安全、完全延迟加载、使用.NET4的Lazy<T>类型。

      1. 非线程安全

    /// <summary>
    /// 非线程安全
    /// </summary>
    public sealed class Singleton1
    {
        /// <summary>
        /// 定义静态变量保存实例
        /// </summary>
        public static Singleton1 Instance = null;

        /// <summary>
        /// 定义私有构造函数保护,使其他地方不得实例
        /// </summary>
        private Singleton1()
        { 
        }
        public string GetString()
        {
          return  "非线程安全的单例模式";
        } 

        /// <summary>
        /// 定义公共方法,实现全局访问
        /// </summary>
        /// <returns></returns>
        public static Singleton1 GetInstance()
        {
            //判断实例状态
            if (Instance==null)
            {
                Instance = new Singleton1();
            }
            return Instance;
        }
    }

    在上述事例中完美的实现了单线程的单例模式的情况。这里我们也需要注意一些的情况:

① 单例类包含一个private的私有构造函数

② 类申明sealed 密封不可继承(不强制)

③ 类中有一个静态变量保存实例

④ 类中提供有一个静态方法或者属性实现实例的创建引用全局调用访问

⑤ 在多线程中单例模式需要另行处理,不然有可能得到类的多个实例

2. 线程安全

    /// <summary>
    /// 线程安全单例模式
    /// </summary>
    public sealed class Singleton2
    {
        /// <summary>
        /// 定义静态变量保存实例
        /// </summary>
        public static Singleton2 Instance = null;

        private static readonly object locks=new object();

        /// <summary>
        /// 定义私有构造函数保护,使其他地方不得实例
        /// </summary>
        private Singleton2()
        {
        }
        public string GetString()
        {
            return "线程安全的单例模式";
        }
        /// <summary>
        /// 定义公共方法,实现全局访问
        /// </summary>
        /// <returns></returns>
        public static Singleton2 GetInstance()
        {
            //对线程进行加锁限制,挂起后来的线程。保证实例安全
            lock (locks)
            {
                if (Instance == null)
                {
                    Instance = new Singleton2();
                }
            } 
            return Instance;
        }
    }

      3. 不用锁线程安全

 /// <summary>
    /// 不用锁线程安全单例模式
    /// </summary>
    public sealed class Singleton3
    {
        /// <summary>
        /// 定义静态变量保存实例
        /// </summary>
        private static readonly Singleton3 Instance = new Singleton3 ();

        static Singleton3()
        { }
        /// <summary>
        /// 定义私有构造函数保护,使其他地方不得实例
        /// </summary>
        private Singleton3()
        {
        }
        public string GetString()
        {
            return "不用锁线程安全单例模式";
        }
        /// <summary>
        /// 定义公共方法,实现全局访问
        /// </summary>
        /// <returns></returns>
        public static Singleton3 GetInstance()
        {   
            return Instance;
        }
    }

      这个实现方法没有使用到锁,但是也实现了线程安全。在第一次调用的时候会创建一个instance。这个实现也有一定的安全隐患。

      1. instance被创建的时机不明,任何对Singleton的调用都会提前创建instance
      2. static构造函数的循环调用。如有A,B两个类,A的静态构造函数中调用了B,而B的静态构造函数中又调用了A,这两个就会形成一个循环调用,严重的会导致程序崩溃。
      3. 我们需要手动添加Singleton的静态构造函数来确保Singleton类型不会被自动加上beforefieldinit这个Attribute,以此来确保instance会在第一次调用Singleton时才被创建。
      4. readonly的属性无法在运行时改变,如果我们需要在程序运行时dispose这个instance再重新创建一个新的instance,这种实现方法就无法满足。

      4. 完全延迟加载

    /// <summary>
    /// 实现完全延迟加载单例模式
    /// </summary>
    public sealed class Singleton4
    {
        /// <summary>
        /// 定义私有构造函数保护,使其他地方不得实例
        /// </summary>
        private Singleton4()
        {
        }
        /// <summary>
        ///  提供访问位置
        /// </summary>
        public static Singleton4 Instance {
            get
            {
                return GetInstance.instance;
            }
        }
        /// <summary>
        /// 定义私有类确保第一次加载是初始化及调用
        /// </summary>
        private class GetInstance
        {
            static GetInstance(){}
            internal static readonly Singleton4 instance = new Singleton4();
        }
         
        public string GetString()
        {
            return "实现完全延迟加载单例模式";
        }
        
    }

      它确保了instance只会在Instance的get方法里面调用,且只会在第一次调用前初始化。是上一个版本的延迟加载的版本

      5. 使用.NET4的Lazy<T>类型

    /// <summary>
    /// 使用Lazy<T>实现完全延迟加载单例模式
    /// </summary>
    public sealed class Singleton5
    {
        /// <summary>
        /// 延迟加载初始化
        /// </summary>
        private static readonly Lazy<Singleton5> lazy=new Lazy<Singleton5>(()=>new Singleton5());

        /// <summary>
        /// 定义私有构造函数保护,使其他地方不得实例
        /// </summary>
        private Singleton5()
        {
        }

        /// <summary>
        /// 提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton5 Instance()
        {
            return lazy.Value;
        }
        public string GetString()
        {
            return "实现完全延迟加载单例模式";
        }

    }

      在.NET4.0中,可以使用Lazy<T> 来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们在写程序时经常会遇到的情形,例如创建某一对象时需要花费很大的开销,而这一对象在系统的运行过程中不一定会用到,这时就可以使用延迟初始化,在第一次使用该对象时再对其进行初始化,如果没有用到则不需要进行初始化,这样的话,使用延迟初始化就提高程序的效率,从而使程序占用更少的内存。

三、使用场合及优缺点

  一、使用场合

1、当类只需要且只能有一个实例并且需要全局访问的时候。

2、当类是使用子类化扩展,并且无需更改代码就可以使用扩展实例的情况下。

二、优点

1、控制实例数量:保证实例数量的唯一且是全局访问。

2、灵活性:类控制了实例化的全过程,这样可以更加灵活的修改实例化过程

3、资源节省:避免对资源的多重占用

三、缺点

1、没有接口、也不能继承。这个与单一责任原则相冲突,一个类只应该负责其逻辑,而不应该去负责如何实例。

四、总结

  在设计模式的学习过程中,单例模式较为简单,实现操作并不是特别难,但是在我们实例运用中也当注意下,比较如果使用出现问题。找到问题还是稍微困难的。这篇文章也介绍了几种单例模式的使用方法,在我们使用时择优选取最佳方案。下一节我们将为全面讲述二级野怪、并学习攻克它。

 生命不息、战斗不止!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 通俗易懂设计模式解析——中介者模式

      今天我们一起看看中介者模式,怎么去理解这个模式呢?说起来也简单、好理解。生活中我们租房经常都是通过中介来实现的。一般租房要么是房东直租要么是中介。那么今天要...

    小世界的野孩子
  • 通俗易懂设计模式解析——访问者模式

      今天我们看的是访问者模式【Visitor Pattern】,我们理解访问者模式这个名称可能会有利于我们理解其核心代码块。我们看这么个例子:我去朋友家做客,那...

    小世界的野孩子
  • 通俗易懂设计模式解析——工厂模式(Factory Method)

      上一篇我们介绍了单例模式,今天给大家讲一个比较简单的模式——工厂模式(Factory Method),工厂模式又是什么呢?顾名思义,工厂——生产制造东西的地...

    小世界的野孩子
  • c#XML配置文件辅助类

    XMLSourceHelp.SH.ConvertIdToName("101", XMLSourceHelp.EXMLDataSource.Lightweigh...

    冰封一夏
  • tf.summary.*函数

    在TensorFlow中,最常用的可视化方法有三种途径,分别为TensorFlow与OpenCv的混合编程、利用Matpltlib进行可视化、利用TensorF...

    周小董
  • Magcodes.WeiChat——自定义CustomCreationConverter之实现微信自定义菜单的序列化

    微信自定义菜单接口是一个比较麻烦的接口,往往开发的小伙伴们看到下面的这段返回JSON,整个人就会不好了:

    雪雁-心莱科技
  • Silverlight Telerik控件学习:带CheckBox复选框的树形TreeView控件

    在web开发中,带checkbox的tree是一个很有用的东东,比如权限选择、分类管理,如果不用sl,单纯用js+css实现是很复杂的,有了SL之后,就变得很轻...

    菩提树下的杨过
  • mvc自定义全局异常处理

      异常信息处理是任何网站必不可少的一个环节,怎么有效显示,记录,传递异常信息又成为重中之重的问题。本篇将基于上篇介绍的html2cancas截图功能,实现mv...

    用户1168362
  • 免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

        很多的软件项目中都会使用到定时任务、定时轮询数据库同步,定时邮件通知等功能。.NET Framework具有“内置”定时器功能,通过System.Tim...

    彭泽0902
  • C# 算法系列一基本数据结构

    作为一个程序员,算法是一个永远都绕不过去的话题,虽然在大学里参加过ACM的比赛,没记错的话,浙江赛区倒数第二,后来不知怎么的,就不在Care他了,但是现在后悔了...

    郑小超.

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动