设计模式之单例

单例模式介绍

单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升整体代码的性能。

在我们平时使用中,要确保一个类只能有一个实例对象,即使多线程同时访问,也只能创建一个实例对象,并需要提供一个全局访问此实例的点。

用来创建独一无二的,只能有一个实例对象的入场卷。

单例模式允许在程序的任何地方访问特定对象,但是它可以保护该实例不被其他代码覆盖。

使用场景:

  • 控制某些共享资源的访问权限(连接数据库、访问特殊文件)
  • 某些全局的属性或变量想保持其唯一性,可使用
  • 程序中的某个类对于所有客户端只有一个可用的实例,可以使用单例模式

代码结构:

  • 将默认的构造函数设为私有,防止其他对象使用单例类的new运算符。
  • 会有一个静态构造方法作为构造函数。该函数会偷偷调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。后面所有对该函数的调用都将返回这一缓存对象。

实现方式:

  • 在类中声明一个私有静态成员变量用于保存单例模式
  • 声明一个公有静态构建方法用于获取单例
  • 在静态方法中实现“延迟初始化”,该方法会在首次被调用时创建一个新对象,并将其存储在静态成员变量中,此后该方法每次被调用时都返回该实例
  • 将类的构造函数私有私有,防止其外部对象声明调用

单例模式优缺点

优点:

  • 可以保证一个类只有一个实例
  • 获得了一个指向该实例的全局访问节点
  • 仅在首次请求单例对象时对其进行初始化

缺点:

  • 违反了“单一职责原则”(该模式同时解决了两个问题)
  • 该模式在多线程中需特殊处理,避免多个线程多次创建单例对象
  • 单元测试比较困难,无法新建声明新的测试对象。

Demo

单例可以分很多实现方式,但是从大类上来划分,主要为懒汉模式和饿汉模式

懒汉模式
  • 懒汉模式(线程不安全)
    /// <summary>
    /// 单例模式 (常规用法,线程不安全。)
    /// </summary>
    public class Singleton
    {       
        /// <summary>
        /// 私有构造函数
        /// </summary>
        private Singleton()
        {

        }
        /// <summary>
        /// 静态局部变量
        /// </summary>
        private static Singleton _instance=null;
        /// <summary>
        /// 静态的全局唯一访问口
        /// 只能得到缓存的静态局部变量实例,无法重新新建。
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance() 
        {
            if (_instance==null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
    

此方法满足了懒加载,但是如果多个访问者同时进行访问获取对象,就会出现多个实例对象,就不是单例模式了,所以此方法在多线程场景是不实用的。

  • 懒汉模式(线程安全)
    /// <summary>
    /// 单例模式 (常规用法,线程安全。)
    /// </summary>
    public class Singleton
    {       
        /// <summary>
        /// 私有构造函数
        /// </summary>
        private Singleton()
        {
        }
        /// <summary>
        /// 静态局部变量
        /// </summary>
        private static Singleton _instance=null;
        /// <summary>
        /// 声明锁 锁同步
        /// </summary>
        private static readonly object _lock = new object();
        /// <summary>
        /// 静态的全局唯一访问口
        /// 只能得到缓存的静态局部变量实例,无法重新新建。
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance(string value) 
        {  
                if (_instance == null)                //双重判空,确保在多线程状态下只创建一个实例对象
                {
                    lock (_lock)                                 //加锁,确保其每次只能由一个对象进行访问
                    {
                        if (_instance==null)
                        {
                            _instance = new Singleton();
                            _instance.Value = value;
                        }
                    }                    
                }            
            return _instance;
        }

        public string Value { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("多线程进行访问");
            Thread processOne = new Thread(() => {
                TestSingleton("阿辉");
            });

            Thread processTwo = new Thread(() => {
                TestSingleton("阿七");
            });

            processOne.Start();
            processTwo.Start();

            processOne.Join();
            processTwo.Join();

            Console.ReadKey();
        }

        static void TestSingleton(string value) 
        {
            Singleton s = Singleton.GetInstance(value);
            Console.WriteLine(s.Value);
        }
    }

可以看到在我们模拟的多线程访问过程中,即使设置的阿辉和阿七,最后输出的也只有阿辉,也就是说只能创建一个实例对象。

可以看到懒加载就是程序刚开始不实例化,只有在被调用或者需要使用它的时候才进行实例化操作,这就是懒加载。

饿汉模式
  • 使用静态变量实现单例 ——饿汉模式(线程安全)
    public class Singleton1
    {
        /// <summary>
        /// 饿汉式,也就是在程序运行时都已经进行了初始化操作,后续只是调用而已。
        /// </summary>
        private static Singleton1 instance = new Singleton1();
        
        private Singleton1()
        {
                
        }

        public static Singleton1 GetInstance() 
        {
            return instance;
        }                   
    }

饿汉式顾名思义就是提前都恶的不行了,在程序刚开始启动的时候就已经将其进行了实例化,后续只管调用。

这种不是懒加载,无论程序是否会用到这个类,它都会提早的进行实例化。

  • 利用静态构造函数实现单例模式(线程安全)
    public class Singleton2 
    {
        private static Singleton2 _Singleton2 = null;

        static Singleton2() {
            _Singleton2 = new Singleton2();
        }

        public static Singleton2 CreateInstance() 
        {
            return _Singleton2;
        }
    }

单例模式常用的就大体介绍完了,我们在平时的使用过程中需要根据不同的业务逻辑来选择不同的实现方式,不能说哪一种最好,也不能说哪一种不好,只是它们使用的场景不同而已。

小寄语

人生短暂,我不想去追求自己看不见的,我只想抓住我能看的见的。

我是阿辉,感谢您的阅读,如果对你有帮助,麻烦点赞、转发 谢谢。