写一个MDI窗体程序,当中有一个是‘工具箱’的窗体,希望工具箱要么不出现,出现也只出现一个,可实际上却是每点击菜单,实例化‘工具箱’,就会出来一个,这样点击多次就会出现很多个‘工具箱’

大话设计模式十九之单例模式

大话设计模式十九之单例模式

每点击一次‘工具箱’的菜单项,就产生一个新的‘工具箱’窗体,但实际上,只希望它出现一次,或者干脆部出现。


一、判断对象是否是null

判断一下FormToolbox有没有实例化过就行了。

这里是在按了菜单按钮时,才去FormToolbox ftb = new FormToolbox();当然是新实例化了。

问题就出现在这里,完全可以把声明的工作放到类的全局变量中完成。这样就可以去判断这个变量是否被实例化过了。

大话设计模式十九之单例模式



如果现在不但要在菜单里启动‘工具箱’,还需要在‘工具栏’上又一个按钮来启动‘工具箱’,这时,如果首先加一个工具栏控件toolStrip1,然后再在按钮toolStripButton1的click事件代码里写统一的代码就可以了。

大话设计模式十九之单例模式

复制粘贴是最容易的编程,但也是最没有价值的编程。现在将两个地方的代码复制在一起,这就是重复。这要是需求变化或又Bug时就需要改多个地方。

把上面的代码抽取成方法来让他们调用。运行程序后,启动‘工具箱’,然后把‘工具箱’窗体关闭,再点启动按钮看看。

‘工具箱’不出现了。原因是当你关闭‘工具箱’时,它的实例并没有变为null而只是Disposed,你的判断只是是否等于null,当然就不会再实例化了。需要增加IsDisposed属性的判断。

大话设计模式十九之单例模式


如果现在又五个地方需要实例化‘工具箱’窗体,这个小bug就需要改动五个地方,复制粘贴很害人。现在将它提炼到一个方法里去。

大话设计模式十九之单例模式


二、

领导问下属,报告交了没有,下属可能说‘早交了’于是领导满意地点点头,下属也可能说‘还剩下一点内容没写,很快上交’,领导皱起眉头说‘要抓紧’,此时这份报告交还是没交,由谁来判断?

当然是下属自己的判断,因为下属最清楚报告交了没有,领导只需要问问就行了。

同样的,现在‘工具箱’FormToolbox是否实例化都是在MDI主窗体Form1代码里判断,这显然不合逻辑。

Form1里应该只是通知启动‘工具箱’,至于‘工具箱’窗体是否实例化过,应该由‘工具箱’自己来判断。

当然,实例化与否的过程其实就和报告交了与否的过程一样,应该由自己来判断,这是它自己的责任,而不是别人的责任。别人应该只是使用它就可以了。


实例化其实就是new的过程,但问题就是我怎么人家不用new呢?

如果不对构造方法改动的话,是不可能阻止他人不去new的。所以我们完全可以直接就把这个类的构造方法改成私有(private)。

所有类都由构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。

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


私有方法外界不能访问,这是对的,但是这样一来,这个类如何能由实例呢?

我们的目的是让这个类只能实例化一次。没有new,现在连一次也不能实例化了。

只能说,对于外部代码,不能用new来实例化它,但是我们完全可以再写一个public方法,叫做GetInstance().这个方法的目的就是返回一个类实例,而此方法中,去做是否由实例化的判断,如果没有实例化过,由调用private的构造方new出这个实例,之所以它可以调用是因为它们在同一个类中,private方法可以被调用的。

大话设计模式十九之单例模式

其实也就是把之前写的代码搬到了‘工具箱’FormToolbox中,由于构造方法私有,就只能从内部去调用。然后当访问静态的共有方法GetInstance()时,它会先去查看内存中由没有这个类的实例,若有就直接返回。

大话设计模式十九之单例模式

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


三、单例模式

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其它实例可以被创建,并且它可以提供一个访问该实例的方法。

大话设计模式十九之单例模式

Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,注意负责创建自己的唯一实例。

大话设计模式十九之单例模式


单例模式处理可以保证唯一的实例外,还有哪些好处?

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

单例优点像一个使用类的静态方法,比如.Net框架里的Math类,有很多数学计算方法,这两者有什么区别呢?

它们之间的确很类似,实用类通常也会采用私有化的构造方法来避免其有实例。但它们还是有很多不同的,比如实用类不保存状态,仅提供一些静态方法或静态属性让你使用,而单例类是由状态的。实用类不能用于继承多态,而单例虽然实例唯一,却是可以由子类来继承。实用类只不过是一些方法属性的集合,而单例却是由着唯一的对象实例。


三、多线程时的单例

多线程的程序中,多个线程同时访问Singleton类,调用GetInstance()方法,会由可能造成创建多个实例的。

lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),知道该对象被释放。

大话设计模式十九之单例模式

这段代码使得对象实例由最先进入的那个线程创建,以后的线程在进入时不会再去创建对象实例了。由于由了lock,就保证了多线程环境下的同时访问也不会造成多个实例的生成。

为什么不直接lock(instance),而是再创建一个syncRoot来lock呢?

加锁时,instance实例有没有被创建过实例都还不知道,怎么能对它枷锁呢?

但这样就得每次调用GetInstance方法时都需要lock,好像不太好。的确是这样,这种做法是会影响性能的,所以对这个类再做改良


四、双重锁定

大话设计模式十九之单例模式

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


在外面已经判断了instance实例是否存在,为什么在lock里面还需要做一次instance实例是否存在的判断呢?

大话设计模式十九之单例模式

对于instance存在的情况,就直接返回,着没有问题。当instance为null并且同时又两个线程调用GetInstance()方法时,它们将都可以通过第一重instance==null的判断。然后由于lock机制,这两个线程只有一饿进入,另一个在外排队等候,必须要其中一个进入并出来后,另一个才能进入。而此时如果没有了第二重的instance是否为null的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,这就没有达到单例的目的。


四、静态初始化

C#与公共语言运行库也提供了一种‘静态初始化’方法,这种方法不需要开发人员显示地编写线程安全代码即可解决多线程环境下它是不安全的问题。

大话设计模式十九之单例模式

这样的实现与前面的示例类似,也是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性为访问实例提供了一个全局访问点。不同之处在于它依赖公共语言运行库开初始化变量。由于构造方法是私有的,因此不能在类本身以外实例化Singleton类;因此,变量引用的是可以在系统中存在的唯一的实例。不过需要注意,instance变量标记为readonly,这意味着只能在静态初始化期间或在类构造函数中分配变量。

这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉单例类。

要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例类。


由于饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问安全性的问题,需要做双重锁定这样的处理才可以保证安全。




相关文章:

  • 2021-06-23
  • 2021-04-15
  • 2021-10-29
  • 2021-05-03
  • 2021-10-29
  • 2021-11-12
  • 2021-12-12
  • 2019-07-27
猜你喜欢
  • 2021-08-12
  • 2022-02-07
  • 2017-12-15
  • 2021-12-30
  • 2021-10-12
相关资源
相似解决方案