设计模式之单例模式

设计模式之单例模式

Intro

一个类只容许建立惟一一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式。git

单例模式多是你们据说最多的设计模式了,网上介绍最多的设计模式大概就是单例模式了,我看过的设计模式相关的文章不少都是写一篇介绍单例模式,而后就没有了。程序员

经典的设计模式有 23 种, 若是随便抓一个程序员,让他说一说最熟悉的 3 种设计模式,那其中确定会包含今天要讲的单例模式,github

使用场景

单例模式主要用来确保某个类型的实例只能有一个。好比手机上的蓝牙之类的只能有一个的实例的场景能够考虑用单例模式。设计模式

主要做用:安全

  • 处理资源访问冲突,好比说上面说的系统惟一硬件,系统文件访问冲突等
  • 表示全局惟一类,好比系统中的惟一 id 生成器

单例模式的实现

单例模式的实现,一般须要私有化构造方法,防止外部类直接使用单例类的构造方法建立对象多线程

简单非线程安全的实现

public class Singleton
{
    private static Singleton _instance;

    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Singleton();
        }

        return _instance;
    }
}

这种方式比较简单,可是不是线程安全的,多线程高并发状况下可能会致使建立多个实例,可是若是你的业务场景容许建立多个,我以为问题也不大,若是必定要保证只能建立一个实例,能够参考下面的作法架构

双检锁(懒汉式)

/// <summary>
/// 双重判空加锁,饱汉模式(懒汉式),用到的时候再去实例化
/// </summary>
public class Singleton
{
    private static volatile Singleton _instance;
    private static readonly object SyncLock = new object();

    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        if (_instance == null)
        {
            lock (SyncLock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
            }
        }

        return _instance;
    }
}

这种方式的执行过程会先检查是否完成了实例化,若是已经实例化则直接返回实例,若是没有就尝试获取锁,得到锁以后再判断一下是否已经实例化,若是已经实例化则返回实例,若是没有就进行实例化并发

静态初始化(饿汉式)

/// <summary>
/// 饿汉模式-就是屌丝,担忧饿死。类加载就给准备好
/// </summary>
public sealed class Singleton1
{
    /// <summary>
    /// 静态初始化,由 CLR 去建立,无需加锁
    /// </summary>
    private static readonly Singleton1 Instance = new Singleton1();

    private Singleton1()
    {
    }

    public static Singleton1 GetInstance() => Instance;
}

这也是一种常见的实现单例模式的用法,可是这种方式就不支持懒加载了,不像上面那种方式能够作到须要的时候再实例化,适用于这个对象会被频繁使用或者这个类比较小,是否实例化没有什么影响。框架

并发字典型

这个是以前忘记在哪里看到的微软框架里的一段代码,相似,可能和源码并不彻底同样,只是提供一种实现思路asp.net

/// <summary>
/// 使用 ConcurrentDictionary 实现的单例方法,用到的时候再去实例化
/// 这种方式相似于第一种方式,只是使用了并发集合代替了双重判断和 lock
/// </summary>
public class Singleton2
{
    private static readonly ConcurrentDictionary<int, Singleton2> Instances = new ConcurrentDictionary<int, Singleton2>();

    private Singleton2()
    {
    }

    public static Singleton2 GetInstance() => Instances.GetOrAdd(1, k => new Singleton2());
}

Lazy

C# 里提供了 Lazy 的方式实现延迟实例化

/// <summary>
/// 使用 Lazy 实现的单例方法,用到的时候再去实例化
/// </summary>
public class Singleton3
{
    private static readonly Lazy<Singleton3>
        LazyInstance = new Lazy<Singleton3>
        (() => new Singleton3());

    private Singleton3()
    {
    }

    public static Singleton3 GetInstance() => LazyInstance.Value;
}

其余

你也可使用内部类等实现方式,这里就不介绍了,想了解能够本身网上找一下

验证是否线程安全,验证示例代码:

Console.WriteLine($"Singleton");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton1");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton1.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton2");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton2.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

Console.WriteLine($"Singleton3");
Enumerable.Range(1, 10).Select(i => Task.Run(() =>
{
  Console.WriteLine($"{Singleton3.GetInstance().GetHashCode()}");
})).WhenAll().Wait();

上面的 WhenAll 是一个扩展方法,就是调用的 Task.WhenAll,输出示例:

单例模式的存在的问题

  • 单例对 OOP 特性的支持不友好,使用单例模式一般也就意味着放弃了 OOP 的继承,多态特性
  • 单例会隐藏类之间的依赖关系,单例模式,不容许显示 new,使得对象的建立过程对外部来讲是不可见的,内部有哪些依赖对外也是不可见的,这样在系统重构的时候就会很危险,很容易形成系统出现问题
  • 单例对代码的扩展性不友好,单例类只能有一个对象实例。若是将来某一天,咱们须要在代码中建立两个实例或多个实例,那就要对代码有比较大的改动
  • 单例对代码的可测试性不友好,若是单例类依赖比较重的外部资源,好比 DB,咱们在写单元测试的时候,但愿能经过 mock 的方式将它替换掉。而单例类这种硬编码式的使用方式,致使没法实现 mock 替换
  • 单例不支持有参数的构造函数,单例模式一般使用私有构造方法,并且只会调用一次构造方法,因此一般不支持构造方法参数,若是有参数一般会给调用方形成误解,两次调用传递的参数不一致的时候如何处理是一个问题

More

随着如今依赖注入思想的普及,asp.net core 更是基于依赖框架构建的,使用依赖注入的方式能够较好的解决上面的各类问题

基于依赖注入框架,你能够没必要担忧对象的建立和销毁,让依赖注入框架管理对象,这样这个要实现单例模式的类型能够和其余普通类型同样,只须要使用依赖注入框架注册服务的时候指定服务生命周期为单例便可,好比使用微软的依赖注入框架的时候可使用 services.AddSingleton<TSingletonService>(); 来注册单例服务

Reference

相关文章
相关标签/搜索