• 第 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();
  • 相关阅读:
    如何在IE, Firefox, Opera浏览器中支持页面弹出并回传数据
    访问IIS 6.0 web服务时, 显示service unavailable
    新blog新气象
    jQuery 参考手册 选择器
    如何在博客中添加目录并链接到当前页面中的某个位置?
    Excel中左中右截取及去空格
    jQuery 遍历 slice() 方法
    如何知道一个工作簿中有多少个工作表?
    jQuery 选择器
    邮政编码查询网址
  • 原文地址:https://www.cnblogs.com/zhangchaoran/p/8465491.html
Copyright © 2020-2023  润新知