服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - 解析C#设计模式之单例模式

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

2022-10-19 13:04yangyang C#

这篇文章主要介绍了C#设计模式之单例模式的相关资料,帮助大家更好的理解和学习c# 设计模式的内容,感兴趣的朋友可以了解下

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

约定单例模式(Singleton by Convention)

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

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

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

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

?
1
2
3
4
public static class Globals
{
  public static Database Database = new Database();
}

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

经典的实现方式

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

?
1
2
3
4
5
public class Database
{
  private Database() { ... }
  public static Database Instance { get; } = new Database();
}

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

延迟初始化

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,不论对象是否已经创建,都需要获取然后释放锁,比较消耗资源,所以,在外面再加一层判断。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中,如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
  }
}

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

?
1
2
3
4
5
6
7
8
9
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方法:

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

简单且完美。

依赖注入与单例模式

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

?
1
services.AddSingleton<Database>();

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

?
1
services.AddSingleton<IDatabase,Database>();

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

?
1
2
3
4
public XXXController(IServiceProvider serviceProvider)
{
   var db = serviceProvider.GetService<IDatabase>();
}

单态模式(Monostate)

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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# 单例模式的资料请关注服务器之家其它相关文章!

原文链接:https://www.yycoding.xyz/post/2020/10/6/introduction-to-design-patterns-of-singleton-pattern

延伸 · 阅读

精彩推荐
  • C#WPF字体或内容模糊的解决方法

    WPF字体或内容模糊的解决方法

    WPF下开发的程序字体模糊,这个问题或许大家都有遇到过,为了解决WPF字体模糊,查阅了各种资料,结果偶然发现是自己疏忽了一些细节造成的,具体是什...

    王旭9722021-12-13
  • C#C# 向Word中设置/更改文本方向的方法(两种)

    C# 向Word中设置/更改文本方向的方法(两种)

    在一般情况下word中输入的文字都是横向的,今天小编给大家带来两种方法来设置更改文本方向的方法,非常不错,对c# word 更改文本方向的知识感兴趣的朋...

    Yesi8522021-12-06
  • C#C#实现绑定DataGridView与TextBox之间关联的方法

    C#实现绑定DataGridView与TextBox之间关联的方法

    这篇文章主要介绍了C#实现绑定DataGridView与TextBox之间关联的方法,涉及C#绑定控件关联性的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    我心依旧6212021-10-20
  • C#c# 实现汉诺塔游戏

    c# 实现汉诺塔游戏

    这篇文章主要介绍了c# 实现汉诺塔游戏的示例,帮助大家更好的理解和使用c# 编程语言,感兴趣的朋友可以了解下...

    黑衫老腰3492022-10-17
  • C#C#实现顺序表(线性表)完整实例

    C#实现顺序表(线性表)完整实例

    这篇文章主要介绍了C#实现顺序表(线性表)的方法,结合完整实例形式分析了顺序表的原理及C#相关实现技巧,需要的朋友可以参考下...

    丛晓男10352021-11-29
  • C#C#(asp.net)多线程用法示例(可用于同时处理多个任务)

    C#(asp.net)多线程用法示例(可用于同时处理多个任务)

    这篇文章主要介绍了C#(asp.net)多线程Thread用法,可用于同时处理多个任务,以简单数学运算为例讲述了Thread类实现多线程的相关技巧,需要的朋友可以参考下...

    smartsmile20126642021-11-24
  • C#Windows系统中使用C#编写蓝牙通信程序的简单实例

    Windows系统中使用C#编写蓝牙通信程序的简单实例

    这篇文章主要介绍了Windows系统中使用C#编写蓝牙通信程序的简单实例,文中的例子使用到了32feet.NET中的InTheHand.Net.Personal类库,需要的朋友可以参考下...

    hzy37745372021-11-18
  • C#C#存储相同键多个值的Dictionary实例详解

    C#存储相同键多个值的Dictionary实例详解

    在本篇文章里小编给大家整理的是关于C#存储相同键多个值的Dictionary实例内容,需要的朋友们可以学习下。...

    Tulip1238682022-08-29