【愚公系列】2023年10月 二十三种设计模式(五)-单例模式(Singleton Pattern)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。
毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。
🚀一、单例模式(Singleton Pattern)
单例模式是一种创建型设计模式,其主要目标是确保一个类仅有一个实例存在,并提供一个全局访问点,以便整个系统可以轻松访问该实例。通过单例模式,我们可以有效地管理和控制某些资源或对象的唯一性,从而避免不必要的重复创建和资源浪费。
🚀二、使用步骤
🔎1.角色
🦋1.1 单例类(Singleton)
单例模式(Singleton Pattern)是一种创建型设计模式,其主要概念和作用如下:
概念:
单例模式旨在确保一个类只能有一个实例,并提供一种全局的方式让程序的其他部分访问这个唯一实例。这意味着无论在系统中的哪个地方请求该类的实例,都将获得相同的对象引用。
作用:
全局访问点:单例模式提供了一个全局访问点,使得其他部分的代码可以方便地获取该单例对象,而不需要直接实例化它。
资源共享:单例模式通常用于管理某些共享资源,例如数据库连接池、日志记录器、配置文件管理器等。通过单例,可以确保这些资源在整个应用程序中只有一个实例,避免了资源的重复创建和浪费。
懒加载:单例模式可以延迟实例化,只有在需要时才创建单例对象,从而节省了系统启动时间和内存消耗。
全局状态管理:某些情况下,需要一个单一的地方来管理全局状态或配置信息。单例模式提供了这样的机制,以确保一致性和可维护性。
单例模式的主要作用是确保一个类在应用程序的生命周期内只有一个实例存在,并提供一种方式来访问这个实例。这有助于管理全局资源、共享对象和维护全局状态,同时提供了懒加载和全局访问的便利性。
🔎2.示例
命名空间SingletonPattern中包含7个单例类,本案例将介绍这7种常见的单例实现方法。
public sealed class Singleton {
private static Singleton _instance = null;
public static Singleton GetInstance() {
if(_instance == null) {
_instance = new Singleton();
Console.WriteLine("Singleton.GetInstance()!");
}
return _instance;
}
}
最常见的单例类,但是无法保证线程安全。因为首次运行时,n个线程可同时到达if(_instance == null),导致_instance可能会被多初始化n-1次(有1次是需要初始化的)。在_instance被初始化之后新启动的线程不会使该情况重现。
public sealed class SingletonSafe {
private static SingletonSafe _instance = null;
private static readonly object _lock = new object();
public static SingletonSafe GetInstance() {
lock (_lock) {
if (_instance == null) {
_instance = new SingletonSafe();
Console.WriteLine("SingletonSafe.GetInstance()!");
}
}
return _instance;
}
}
使用私有静态object类型的锁(微软推荐),lock关键字会占有该锁,之后请求该锁的其它线程必需等待其释放才能进入。该方法可实现线程安全的单例模式,但是锁属于昂贵资源,“占有锁”和“释放锁”都比较耗时,并会在一定程度上阻止其它线程的执行,会显著影响程序的并发性,所以有了下面的优化。
public sealed class SingletonSafe2 {
private static SingletonSafe2 _instance = null;
private static readonly object _lock = new object();
public static SingletonSafe2 GetInstance() {
if (_instance == null) {
lock (_lock) {
if (_instance == null) {
_instance = new SingletonSafe2();
Console.WriteLine("SingletonSafe2.GetInstance()!");
}
}
}
return _instance;
}
}
通过优先使用if (_instance == null)这种耗费资源较少的比较来决定是否进入锁,可大幅度提高性能。因为_instance不为null时,直接返回即可。
public sealed class SingletonLazy {
private static readonly Lazy<SingletonLazy> _instance =
new Lazy<SingletonLazy>(() => {
Console.WriteLine("SingletonLazy.GetInstance()!");
return new SingletonLazy();
});
public static SingletonLazy GetInstance() {
return _instance.Value;
}
}
带泛型的Lazy式单例实现,这是线程安全的,仅提供给大家参考。
public sealed class SingletonReadOnly {
private static readonly SingletonReadOnly _instance =
new SingletonReadOnly();
public SingletonReadOnly() {
Console.WriteLine("SingletonReadOnly.GetInstance()!");
}
public static SingletonReadOnly GetInstance() {
return _instance;
}
}
静态只读式单例实现(由运行时保证唯一),这是线程安全的。
public abstract class SingletonGenericBase<T> where T : class, new() {
private static T _instance = null;
private static readonly object _lock = new object();
public static T GetInstance() {
if (_instance == null) {
lock (_lock) {
if (_instance == null) {
_instance = new T();
Console.WriteLine("SingletonGeneric.GetInstance()!");
}
}
}
return _instance;
}
}
public sealed class SingletonGeneric : SingletonGenericBase<Singleton> {
public SingletonGeneric() { }
}
复杂的泛型实现,这是线程安全的。
public abstract class SingletonGenericBase2<T> where T : class {
private static readonly Lazy<T> _instance = new Lazy<T>(() => {
var ctors = typeof(T).GetConstructors(
BindingFlags.Instance
| BindingFlags.NonPublic
| BindingFlags.Public);
if (ctors.Count() != 1)
throw new InvalidOperationException(
String.Format("Type {0} must have exactly one constructor.",
typeof(T)));
var ctor = ctors.SingleOrDefault(
c => !c.GetParameters().Any() && c.IsPrivate);
if (ctor == null)
throw new InvalidOperationException(
String.Format("The constructor for {0} must be private and take no parameters.",
typeof(T)));
Console.WriteLine("SingletonGeneric2.GetInstance()!");
return (T)ctor.Invoke(null);
});
public static T GetInstance() {
return _instance.Value;
}
}
public sealed class SingletonGeneric2 : SingletonGenericBase2<SingletonGeneric2> {
private SingletonGeneric2() { }
}
复杂的泛型实现,这是线程安全的。
public class Program {
public static void Main(string[] args) {
var singleton = Singleton.GetInstance();
singleton = Singleton.GetInstance();
var singletonSafe = SingletonSafe.GetInstance();
singletonSafe = SingletonSafe.GetInstance();
var singletonSafe2 = SingletonSafe2.GetInstance();
singletonSafe2 = SingletonSafe2.GetInstance();
var singletonReadOnly = SingletonReadOnly.GetInstance();
singletonReadOnly = SingletonReadOnly.GetInstance();
var singletonLazy = SingletonLazy.GetInstance();
singletonLazy = SingletonLazy.GetInstance();
var singletonGeneric = SingletonGeneric.GetInstance();
singletonGeneric = SingletonGeneric.GetInstance();
var singletonGeneric2 = SingletonGeneric2.GetInstance();
singletonGeneric2 = SingletonGeneric2.GetInstance();
Console.ReadKey();
}
}
以上是调用方的代码,每个GetInstance方法均调用2次以展示效果。以下是这个案例的输出结果:
Singleton.GetInstance()!
SingletonSafe.GetInstance()!
SingletonSafe2.GetInstance()!
SingletonReadOnly.GetInstance()!
SingletonLazy.GetInstance()!
SingletonGeneric.GetInstance()!
SingletonGeneric2.GetInstance()!
🚀总结
🔎1.优点
单例模式的优点包括:
只有一个实例:通过单例模式,我们可以确保一个类只有一个实例存在,避免了由于多个实例造成的资源浪费和冲突的问题。
全局访问:单例模式可以将该类的实例对象进行全局访问,方便其他模块对该实例的调用和操作。
节省系统资源:由于单例模式只有一个实例,避免了重复创建对象和频繁销毁的过程,节省了系统的资源。
具有扩展性:由于单例模式只有一个实例,代码的编写更加简洁明了,也更容易扩展和维护。
单例模式通过控制实例的创建和访问,可以帮助我们更加有效地管理对象,并提高系统的性能和可维护性。
🔎2.缺点
单例模式的缺点包括:
破坏单一职责原则:因为单例类既要负责自身的职责,又要负责控制自身的实例化和访问,因此可能会出现职责不清的情况。
对扩展不友好:单例类的实现往往是硬编码的,这意味着它们很难进行扩展和修改。
违反依赖倒置原则:单例类往往是其他类的依赖对象,如果单例类出现问题,会导致其他类无法正常工作。
难以进行单元测试:由于单例类的实例化和访问具有全局性,因此很难进行单元测试。
单例模式在某些情况下确实会带来一些问题,因此在使用时需要慎重考虑。
🔎3.使用场景
单例模式适用于以下场景:
当系统中需要一个对象来协调行为,以避免多个用户进程访问同一个共享资源时的冲突。
当对象需要被实例化一次,且在运行时动态配置。
当对象的存在需要占用大量的系统资源,例如数据库连接池等。
当一个类只需存在一个实例,而且该实例需要被全局访问。
当需要在系统中保证某个实例的唯一性,避免创建多个实例导致冲突。
单例模式通常应用于应用程序中的某些核心组件,例如配置管理器、数据库连接池、日志管理器和缓存管理器等。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)