第 21 章 —— 单例模式

    (复制粘贴是最容易的编程,但也是最没有价值的编程。复制粘贴就会造成重复,当需求变化或有 Bug 时就需要改多个地方)

    把声明的工作放到类的全局变量中完成(private FormToolbox ftb;),这样就可以在方法中判断这个变量是否实例化过了。

21.3 是否实例化是自己的责任

    单例类是否实例化过,应该由单例类自己判断。(而不是将判断的代码放到调用类当中)

    (如果已经实例化过,则不需要再实例化)实例化其实就是 new 的过程,而如果不对构造方法做改动的话,是不可能阻止他人不去用 new 的,所以我们完全可以直接把这个类的构造方法改成私有(private)。

  于是,只要将“工具箱”类的构造方法写成 private 的,那么外部程序就不能用 new 来实例化它了。

    对于外部代码,不能用 new 来实例化它,但是我们完全可以再写一个 public 方法,叫做 GetInstance(),这个方法的目的就是返回一个类实例,而此方法中,去做是否有实例化的判断。

  如果没有实例化过,由调用 private 的构造方法 new 出这个实例,之所以它可以调用是因为它们在同一个类中,private 方法可以被调用。

  参考代码:

      public class FormToolbox : Form
      {
          private static FormToolbox ftb = null;      //声明一个静态的类变量(全局可用)

          private FormToolbox()                       //构造方法私有,外部代码不能直接用new来实例化它
          {
              //InitializeComponent();    //这个方法也许只有窗体才有,其他的类只要空着就行
          }

          public static FormToolbox GetInstance()     //得到类实例的方法,返回值就是本类对象,注意也是静态的
          {
              if(ftb == null || ftb.IsDisposed)
              {
                  ftb = new FormToolbox();            //当内部的ftb是null或者被Disposed过,则new它。此时将实例化的对象存在静态的变量ftb中,以后就可以不用实例化而得到它
                  //可以在这里继续设置ftb的其他属性
              }
              return ftb;
          }
      }

    (当窗体关闭后,实例并没有变为 null,而只是 Disposed —— if (ftb == null || ftb.IsDisposed))

    这样一来,客户端不再考虑是否需要去实例化的问题,而把责任都给了应该负责的类去处理。这就是单例模式。

21.4 单例模式

    单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。(因为是全局访问点,所以该方法必须是静态的

    通常,我们可以使用一个全局变量(static)使得一个对象被访问,但它不能防止你实例化多个对象(对象的属性也可能会发生改变)。一个最好的办法就是,让类自身负责保存它的唯一实例

  这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法(即返回该实例的静态方法)。

    单例模式的好处:

      因为单例模式封装了它的唯一实例,所以它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。

    单例与实用类(如Math类)的静态方法类似,实用类通常也会采用私有化的构造方法来避免其有实例。

    但它们也有很多的不同,比如:

      实用类不保存状态,仅提供一些静态方法或静态属性让你使用,而单例类是有状态的。

      实用类不能用于继承多态,而单例虽然实例唯一,却是可以有子类来继承。

      实用类只不过是一些方法、属性的集合,而单例却是有着唯一对象的实例。

★21.5 多线程时的单例

    另外,还需要注意一些细节,比如说,多线程的程序中当多个线程同时(注意是同时)访问单例类,调用GetInstance()方法,是会有可能造成创建多个实例的

    解决的方法是给进程加锁来处理:

      lock 语句的含义—— lock 可以确保当一个线程位于代码的临界区时,另一个线程不进入临界区。(如果其他线程视图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放)

    (当写在了 .NET 网站程序的启动部分时应该就不用考虑这个多线程的问题了,因为它的实例化并不是由线程发起的)

      public class Singleton
      {
          private static Singleton instance;
          private static readonly object syncRoot = new object();     //程序运行时创建一个静态只读的进程辅助对象
          private Singleton()
          {

          }

          public static Singleton GetInstance()
          {
              lock(syncRoot)                          //在同一个时刻,加了锁的那部分程序只有一个线程可以进入
              {
                  if(instance==null)
                  {
                      instance = new Singleton();
                  }
              }
              return instance;
          }
      }

    由于有了 lock,就保证了多线程环境下的同时访问也不会造成多个实例的生成。(我们并不知道 instance 实例有没有被创建过,所以无法对它加锁,于是创建了一个 syncRoot 并选择对其加锁)

    但是,当前这种写法,每次调用 GetInstance 方法时都需要加锁,这种做法是会影响性能的,所以可以对这个类做如下改良:

21.6 双重锁定

    public class Singleton
    {
        private static Singleton instance;
        private static readonly object syncRoot = new object();     //程序运行时创建一个静态只读的进程辅助对象
        private Singleton()
        {

        }

        public static Singleton GetInstance()
        {
            if(instance==null)              //先判断实例是否存在,不存在时再加锁处理
            {
                lock (syncRoot)                          //在同一个时刻,加了锁的那部分程序只有一个线程可以进入
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

    现在这样就不用每次都让线程加锁了,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。这种做法被称为 Double-Check Locking(双重锁定)

    这里为什么要两次判断 instance 是否为空呢?

      当 instance 为 null 并且同时有两个线程调用 GetInstance()方法时,它们将都可以通过第一重 instance == null 的判断。然后由于 lock 机制,这两个线程只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。

    而此时如果没有了第二重的 instance 是否为 null 的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。

21.7 静态初始化

    静态初始化的实现方式更为简单,同时也有着与单例模式类似的特性,但两者仍有一些不同,比如静态初始化只能在静态初始化期间或在类构造函数中分配变量(instance 变量标记为 readonly)

附加:单例模式的继承

  父类:

public class Singleton<T> where T : class, new()
{
    private static T _instance;  //使用了泛型T,而子类中又用自身进行泛型,所以这里可以直接将 _instance 理解为子类实例(也是子类唯一实例)
    private static readonly object syslock = new object();
    public static T getInstance()
    {   //线程安全锁
        if (_instance == null)
        {
            lock (syslock)
            {
                if (_instance == null)
                {
                    _instance = new T();
                }
            }
        }
        return _instance;
    }  
}

  子类需要泛型一下:

public class Two : Singleton<Two>
{
    public void Show()
    {
        Console.WriteLine("Two Sub class.......");
    }
}

  调用:

    Two.getInstance().Show();
原文地址:https://www.cnblogs.com/zhangchaoran/p/8465491.html