解析C#设计模式之单例模式

单例模式(Singleton),故名思议就是说在整个应用程序中,某一对象的实例只应该存在一个。比如,一个类加载数据库中的数据到内存中以提供只读数据,这就很适合使用单例模式,因为没有必要在内存中加载多份相同的数据,另外,有些情况下不允许内存中存在多分份相同的数据,比如数据过大,内存容不下两份相同数据等等。

约定单例模式(Singleton by Convention)

这种方式有点“Too simple, Sometimes naïve”,他就是提示使用者,我是单例,不要重复初始化我,比如:

public class Database
{
  /// <summary>
  /// 警告,这是单例,不要初始化多次,否则,后果自负.
  /// </summary>
  public Database() {}
};

一种情况是,根本不会注意到这个提示,其次是在很多时候,这些初始化是偷偷摸摸无意中发生的,比如通过反射,通过工厂产生(Activator.CreateInstance),通过注入等等,虽然有一个“约定大于配置”,但是这里不使用。

单例模式最常见的想法是提供一个全局的,静态的对象。

public static class Globals
{
  public static Database Database = new Database();
}

这种方式并没有很安全。这个并没有阻止用户在其他地方new Database,并且,用户可能并不知道有一个Globals类,里面有个Database单例。

经典的实现方式

唯一的方式阻止用户实例化对象是将构造函数变成私有的,并且提供方法或者属性返回唯一的内部对象。

public class Database
{
  private Database() { ... }
  public static Database Instance { get; } = new Database();
}

现在将构造函数设置为了私有,当然设为私有依旧可以通过反射继续调用,但是这毕竟需要额外操作,这已经可以阻止大部分用户直接实例化了。通过将实例定义为static静态, 使得其生命周期延长至应用程序运行期间。

延迟初始化

以上方法线程安全,但是因为是静态属性,在类的所有实例创建之前,或者任何静态成员访问之前就会初始化,并且在每个AppDomain里都只会初始化一次。

如何实现延迟初始化,即将单例对象的构造推迟到应用程序首次请求该对象进行时,如果应用程序永远不请求对象,则对象永远不会构造。在之前,可以使用double check方式。其实要实现正确的double check还是有些问题需要注意,比如上面这个例子,第一版可能这么写,用一个锁。

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }

  public static Database GetInstance()
  {
    lock (olock)
    {
      if (db == null)
      {
        db = new Database();
      }
      return db;
    }
  }
}

这虽然线程安全,但是每次访问GetInstance,不论对象是否已经创建,都需要获取然后释放锁,比较消耗资源,所以,在外面再加一层判断。

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }

  public static Database GetInstance()
  {
    if (db == null)
    {
      lock (olock)
      {
        if (db == null)
        {
          db = new Database();
        }
      }
    }
    return db;
  }
}

在访问对象之前判断是否已经初始化,如果初始化直接返回,这样就避免了一次对锁的访问。But,这里仍然存在问题。假设Database初始化起来耗时,当线程A获得锁正在对db进行初始化的时候,线程B在最外层判断db是否为空,这个时候,线程A正在初始化db,有可能只初始化了部分,这个时候db就可能不为空,直接返回了没有完全初始化完全的对象,这可能导致线程B崩溃。

解决方式是,将对象存储到临时变量中,然后以原子写的方式存储到db中,如下

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }

  public static Database GetInstance()
  {
    if (db == null)
    {
      lock (olock)
      {
        if (db == null)
        {
          var temp = new Database();
          Volatile.Write(ref db, temp);
        }
      }
    }
    return db;
  }
}

非常繁琐,虽然实现了延迟初始化,但是跟开头的静态字段对比,复杂太多,而且一不小心就会写错。虽然可以简化为:

public static Database GetInstance()
{
  if (db == null)
  {
    var temp = new Database();
    Interlocked.CompareExchange(ref db, temp, null);
  }
  return db;
}

该方法看起来没有用锁,temp对象有可能会被初始化两次,但是在将temp写入到db的时候,Interlock.CompareExchange会保证只会有1个对象正确被写入到db,没有被写入的temp对象会被垃圾回收,这种方式速度比上述的double check要快。但仍然需要学习成本。幸好,C#提供了Lazy方法:

private static Lazy<Database> db = new Lazy<Database>(() => new Database(), true);
public static Database GetInstance() => db.Value;

简单且完美。

依赖注入与单例模式

前面的单例模式其实是一种代码侵入的做法,就是要想一个原本没有实现单例的代码要实现单例,需要修改代码实现,并且这个代码还容易出错。有些人认为单例模式的唯一正确的做法就是在IOC依赖注入中,这样不需要修改源代码,通过依赖注入框架来实现依赖注入,在统一的入口,统一的管理生命周期,在ASP.NET Core MVC中,在Startup的ConfigureServices代码中:

services.AddSingleton<Database>();

或者加入需要用IDatabase的地方,要用Database单例的话:

services.AddSingleton<IDatabase,Database>();

在ASP.NET Core MVC的后续代码中,只要用到IDatabase的地方,就会用Database的单例来实现,不需要我们在Database内做任何修改。在使用的时候,只需要引用IServiceProvider接口里的GetService方法,IServiceProvider是由ASP.NET Core MVC的IOC框架直接提供,不需要特别处理:

public XXXController(IServiceProvider serviceProvider)
{
   var db = serviceProvider.GetService<IDatabase>();
}

单态模式(Monostate)

单态模式是单例模式的一个变种,它是一个普通的类,但是其行为和表现就像单例模式。

比如我们在对一个公司的人员结构进行建模,一个典型的公司一般只会有一个CEO,

public class ChiefExecutiveOfficer
{
  private static string name;
  private static int age;

  public string Name
  {
    get => name;
    set => name = value;
  }

  public int Age
  {
    get => age;
    set => age = value;
  }
}

这个类的属性有get,set,但是其背后的私有字段都是静态的。所以不管这个ChiefExecutiveOfficer实例化多少次,其内部引用的都是同一个数据。比如,可以实例化两个对象,但是其内部的内容是一模一样的。

单态模式简单,但是容易引起混乱,所以要想简单,要想实现单例效果,最好还是使用IOC这种依赖注入的框架,让它来帮助我们来管理实例及其生命周期。

以上就是解析C#设计模式之单例模式的详细内容,更多关于c# 单例模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • c#设计模式之单例模式的实现方式

    场景描述 单例模式对于我们来说一点也不模式,是一个常见的名称,单例模式在程序中的实际效果就是:确保一个程序中只有一个实例,并提供一个全局访问点,节省系统资源 单例模式无论是在实际开发中还是在软件应用中比较常见,比如,windows系统的任务管理器.IIS的HttpApplication.实际项目中的日志组件等等 实现方式 单例模式为了实现一个实例,那么只有不把实例创建暴露出去,只通过类本身来创建实例,为了实现效果,需要定义一个私有构造函数 单例模式实现方式有:饿汉式.懒汉式.双重验证式.静态内部

  • C#设计模式之单例模式实例讲解

    前言 最近开始花点心思研究下设计模式,主要还是让自己写的代码可重用性高.保证代码可靠性.所谓设计模式,我找了下定义:是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.毫无疑问,设计模式于己于他人于系统都是多赢的:设计模式使代码编制真正工程化:设计模式是软件工程的基石脉络,如同大厦的结构一样. 为什么要提倡"Design Pattern(设计模式)"? 根本原因是为了代码复用,增加可维护性.因此这次我们来学习下设计模式,最后会通过C#语言来实现这些设计模式作为例子,深刻

  • C# 设计模式系列教程-单例模式

    1. 描述: 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 2. 单例模式主要有3个特点,: 2.1 单例类确保自己只有一个实例. 2.2 单例类必须自己创建自己的实例. 2.3 单例类必须为其他对象提供唯一的实例. 3. 实现方式:懒汉单例类和饿汉单例类 3.1 懒汉式单例类 对于懒汉模式,我们可以这样理解:该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备.它在需要对象的时候,才判断是否已有对象,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返

  • 关于c#中单例模式的一些问题

    本文主要介绍了关于单例模式的一些问题,想学习C#单例模式的同学们可以看一看,还是有些帮助 c#中的单例模式 单例模式是指在设计一个类时,保证在运行期间只有一个实例对象,因为过多相同的实例对象会占用内存空间. ##举个例子 1.声明一个静态的Class1类的变量来引用唯一的对象. 2.创造私有的无参构造方法,使外部无法调用这个类的构造方法. 3.创建静态的方法,创建此类唯一的对象. 4.通过TempClass1 = new Class1();调用私有构造方法创建该实例. #单例模式的一些特点 1.

  • c# 单例模式的实现方法

    单例模式大概是所有设计模式中最简单的一种,如果在面试时被问及熟悉哪些设计模式,你可能第一个答的就是单例模式. 单例模式的实现分为两种:饿汉式和懒汉式.前者是在静态构造函数执行时就立即实例化,后者是在程序执行过程中第一次需要时再实例化.两者有各自适用的场景,实现方式也都很简单,唯一在设计时要考虑的一个问题就是:实例化时需要保证线程安全. 饿汉式 饿汉式实现很简单,在静态构造函数中立即进行实例化: public class Singleton { private static readonly Si

  • 浅谈C#单例模式的实现和性能对比

    简介 单例指的是只能存在一个实例的类(在C#中,更准确的说法是在每个AppDomain之中只能存在一个实例的类,它是软件工程中使用最多的几种模式之一.在第一个使用者创建了这个类的实例之后,其后需要使用这个类的就只能使用之前创建的实例,无法再创建一个新的实例.通常情况下,单例会在第一次被使用时创建.本文会对C#中几种单例的实现方式进行介绍,并分析它们之间的线程安全性和性能差异. 单例的实现方式有很多种,但从最简单的实现(非延迟加载,非线程安全,效率低下),到可延迟加载,线程安全,且高效的实现,它们

  • 举例讲解C#编程中对设计模式中的单例模式的运用

    单例模式的介绍 说到单例模式,大家第一反应应该就是--什么是单例模式?,从"单例"字面意思上理解为--一个类只有一个实例,所以单例模式也就是保证一个类只有一个实例的一种实现方法罢了,下面给出单例模式的一个官方定义:确保一个类只有一个实例,并提供一个全局访问点.为了帮助大家更好地理解单例模式,大家可以结合下面的类图来进行理解,以及后面也会剖析单例模式的实现思路: 为什么会有单例模式 看完单例模式的介绍,自然大家都会有这样一个疑问--为什么要有单例模式的?它在什么情况下使用的?从单例模式的

  • C# 设计模式之单例模式归纳总结

    优缺点  优点: 一.实例控制 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例. 二.灵活性 因为类控制了实例化过程,所以类可以灵活更改实例化过程. 缺点: 一.开销 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销.可以通过使用静态初始化解决此问题. 二.可能的开发混淆 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象.因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自

  • 解析C#设计模式之单例模式

    单例模式(Singleton),故名思议就是说在整个应用程序中,某一对象的实例只应该存在一个.比如,一个类加载数据库中的数据到内存中以提供只读数据,这就很适合使用单例模式,因为没有必要在内存中加载多份相同的数据,另外,有些情况下不允许内存中存在多分份相同的数据,比如数据过大,内存容不下两份相同数据等等. 约定单例模式(Singleton by Convention) 这种方式有点"Too simple, Sometimes naïve",他就是提示使用者,我是单例,不要重复初始化我,比

  • 全面解析Java设计模式之单例模式

    本文实例为大家分享了Java设计模式之单例模式的具体代码,供大家参考,具体内容如下 概念: 单例模式:一个类中只有一个实例. 一个类有且仅有一个实例,并且提供了一个全局的访问点. 使用该模式的起因: 当我们在浏览网站时,有些网站会显示"当前在线人数".通常,实现这个功能的办法是将登陆的每一个IP存储在一个内存.文件或者数据库中,每多一个IP,就实现"+1".一般就是用一个方法,比如add(),实现"+1"的功能,比如用"update&q

  • 解析Javascript设计模式Revealing Module 揭示模式单例模式

    目录 1. Revealing Module 揭示模式 2. Singleton 单例模式 1. Revealing Module 揭示模式 该模式能够在私有范围内简单定义所有的函数和变量,并返回一个匿名对象, 它拥有指向私有函数的指针,该函数是他希望展示为公有的方法. 示例: <script> var myRevealingModule = function () { var privateVar = "Ren Cherry", publicVar = "Hey

  • Java设计模式之单例模式实例分析

    本文实例讲述了Java设计模式之单例模式.分享给大家供大家参考,具体如下: 单例模式:(Singleton Pattern)是一个比较简单的模式,其定义如下: Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例) 单例模式,很简单的一个模式.其实在android开发中,很多地方都会用到单例模式,比如某些工具类.Json数

  • Java设计模式之单例模式深入探索

    目录 什么是设计模式? 单例模式是什么? 单例模式设计的原则是什么? Java实现单例模式的5种方式? 懒汉 饿汉 静态内部类 双重校验锁DCL(Double Check Lock) 枚举(num) 小结 ❤️‍您好,我是贾斯汀,今天来聊一聊单例模式!❤️‍ 什么是设计模式? 百科: 设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结. 设计模式是软件行业的通用的设计标准,在Java同样通用,主要有23种设计模式如下: 有的小伙伴可能会问,这么多,学得完吗? 答:不好意思

  • php设计模式之单例模式代码

    php设计模式之单例模式的例子,供大家参考,具体内容如下 <?php /** * php设计模式 单例模式 */ class Fruit{ private static $instanceMap = array(); //protected getter for singleton instances protected static function getSingleton($className){ //保证单例模式 并且不能从控制器实例化和克隆 if (!isset(self::$inst

  • php设计模式之单例模式实例分析

    本文实例讲述了php设计模式之单例模式.分享给大家供大家参考.具体分析如下: 单例模式(职责模式): 简单的说,一个对象(在学习设计模式之前,需要比较了解面向对象思想)只负责一个特定的任务: 单例类: 1.构造函数需要标记为private(访问控制:防止外部代码使用new操作符创建对象),单例类不能在其他类中实例化,只能被其自身实例化: 2.拥有一个保存类的实例的静态成员变量 3.拥有一个访问这个实例的公共的静态方法(常用getInstance()方法进行实例化单例类,通过instanceof操

  • java设计模式之单例模式的详解及优点

    java设计模式之单例模式 定义:如果一个类始终只能创建一个实例,那么这个类被称为单例类,这种设计模式被称为单例模式. Spring框架里面可以将所有生成的bean对象都设置为单例模式,只需要在配置Bean实例时指定scope="singleton"即可,或者不做配置默认即为单例模式. 我们可以创建一个小的Demo来演示单例模式的实现,只需要保证该类只能创建一个实例,我们可以用权限修饰符private修饰该类的构造器. 提供一个创建该类的接口,该接口只能用static修饰,类里面创建一

  • Java设计模式之单例模式实例详解【懒汉式与饿汉式】

    本文实例讲述了Java设计模式之单例模式.分享给大家供大家参考,具体如下: 单例模式就是产生一个对象实例,供外外部访问. 它的应用场景就是在这个类在全局真资源需要统一访问,否则会造成混乱时,才有必要设计成单例. 懒汉式,就是在使用这个对象时,才去查看这个对象是否创建,如果没创建就马上创建,如果已经创建,就返回这个实例. 饿汉式,在加载这个类的时候就先创建好一个对象实例,等待调用. 两者的优缺点也能猜到,使用懒汉式,在反应速度上肯定要比饿汉式慢. 但是这个对象如果不被调用,那就节省了cpu和内存资

  • java 设计模式之单例模式

    java  设计模式之单例模式 前言: 在软件开发过程中常会有一些对象我们只需要一个,如:线程池(threadpool).缓存(cache).对话框.偏好设置等.这些对象如果制造出多个实例的话可能会导致一些不必要的麻烦,如:程序行为异常.资源使用过量等.这时单例模式就可以确保一个类只有一个实例,并提供全局访问点.下面是从简单的单例类来探讨该用何种方法实现单例模式. /** * 最经典的单例类 */ public class Singleton { // 设置成静态变量来记录Singleton的唯

随机推荐