单例模式(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# 单例模式的资料请关注小牛知识库其它相关文章!
本文向大家介绍C#设计模式之单例模式实例讲解,包括了C#设计模式之单例模式实例讲解的使用技巧和注意事项,需要的朋友参考一下 前言 最近开始花点心思研究下设计模式,主要还是让自己写的代码可重用性高、保证代码可靠性。所谓设计模式,我找了下定义:是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工
介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现。OK,正式开始。 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯
本文向大家介绍java 设计模式之单例模式,包括了java 设计模式之单例模式的使用技巧和注意事项,需要的朋友参考一下 java 设计模式之单例模式 前言: 在软件开发过程中常会有一些对象我们只需要一个,如:线程池(threadpool)、缓存(cache)、对话框、偏好设置等。这些对象如果制造出多个实例的话可能会导致一些不必要的麻烦,如:程序行为异常、资源使用过量等。这时单例模式就可以确保
本文向大家介绍java设计模式之单例模式,包括了java设计模式之单例模式的使用技巧和注意事项,需要的朋友参考一下 单例模式也叫做单肩模式,也是一种创建型模式,是我们日常开发中最常使用的一种设计模式,经常被用来封装一些工具类,例如数据库连接等。 单例模式的定义: 单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类
本文向大家介绍Android 单例模式 Singleton 简单实例设计模式解析,包括了Android 单例模式 Singleton 简单实例设计模式解析的使用技巧和注意事项,需要的朋友参考一下 单例模式 Singleton 简单实例设计模式解析 前言 今天我来全面总结一下Android开发中最常用的设计模式 - 单例模式。 关于设计模式的介绍,可以看下我之前写的:1分钟全面了解“设计模式” 目录
本文向大家介绍C++设计模式之简单工厂模式实例,包括了C++设计模式之简单工厂模式实例的使用技巧和注意事项,需要的朋友参考一下 问题描述 之前在公司做了一个windows 8平台的阅读器。首先,需要将电子书中的内容渲染到屏幕上,而电子书每一页都包含各种各样的内容,比如:图形、图像和文字等等;不同的内容,就是不同的对象;在将不同的内容渲染到屏幕上之前,就需要new操作,建立不同的对象,然后再在屏幕上