1.前言
古代兵法鼻祖孙武在研究了历代战例后,编写了被誉为千古绝唱《孙子兵法》。任何一种模式都不是凭空出现的,都是经过不断总结历史经验教训而得出的最佳实践,设计模式亦是如此。学习设计模式的目的就是要利用已有的解决方案去解决相同或者类似情形下的问题。解决问题的前提是准确识别问题,毛主席说过调研就像是十月怀胎,解决问题就像是一朝分娩。这句话道出了识别问题和解决问题的关系。所以学习设计模式不可以单纯地学习模式本身,否则成为了招式,没有了"内功心法"的招式不能拆分组合,真到了要解决实际问题的时候只能是束手待毙。设计模式不是凭空产生,也不是一下子就出现最终完善的模式或者架构的,都是经过了多次地完善,每一次地完善都是为了解决目前模式所无法解决的问题或者规避即将出现的问题,所以,每一步完善的原因非常重要,这些原理性的东西就是"内功心法".
2.单例模式
单例模式的要求:
1)单例模式是为了确保类只有一个实例。
2)只有唯一一个全局访问点。
类的实例化是通过构造函数来实现的。当类没有显式的构造函数时,编译器会给类提供一个默认的无参数构造函数,使用new 关键字来实例化类时,这个默认构造函数会被调用;当类有显式构造函数时,编译器会调用类的显式构造函数。显式构造函数可以是有参数或者无参数的构造函数。如果外部能够使用new关键字时,那么每次new 都会出现一个类的实例,这就违背了单例模式的初衷,所以,应该让外部不能使用new。如果我们写一个无参数构造函数,并且控制外部不能访问这个构造函数,那么我们的目的就达到了。
如何提供唯一的全局访问点呢?既然外部不能使用new来创建实例,那么使用静态字段来存储类的实例,并且设置访问范围为公有的,这样就能够解决全局访问点的问题,这样我们就可以满足上述两个要求。
根据上述原理,我们得到如下实现方式:
using System; using System.Collections.Generic; using System.Linq; using System.Text; public class Sington { private static Sington singtonInstance = null; private Sington() { } public static Sington GetInstance() { if (singtonInstance == null) singtonInstance = new Sington(); return singtonInstance; } } public class NotSington { }
注:这里多出来的内容后面会用到。
我们还需要一个类来测试:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TestSington { class Program { static void Main(string[] args) { //private constructor can not be called Sington sington1 = Sington.GetInstance(); Sington sington2 = Sington.GetInstance(); int hashCode1 = sington1.GetHashCode(); int hashCode2 = sington2.GetHashCode(); NotSington notSington1 = new NotSington(); NotSington notSington2 = new NotSington(); int hashCode3 = notSington1.GetHashCode(); int hashCode4 = notSington2.GetHashCode(); if (sington1.Equals(sington2)) Console.WriteLine("true"); else Console.WriteLine("false"); if (hashCode3 == hashCode4) Console.WriteLine("true"); else Console.WriteLine("false"); Console.ReadKey(); } } }
我们知道类的实例是否相同,我们使用HashCode来判断,我们可以看到sington1 和 sington2 是相同的,因为HashCode相同,而notSington1 和notSington2不同。这样我们就可以通过Sington类的静态方法获取唯一的Sington实例。
这时候有人会考虑到多线程的程序如果同时访问代码时可能还是会创建两个实例,这时候我们就在线程级别做一个锁定来解决这个问题,同一时间只允许一个线程来操作这个类的GetInstance方法即可。
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton instance; // 定义一个标识确保线程同步 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 当第一个线程运行到这里时,此时会对locker对象 "加锁", // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (instance == null) { instance = new Singleton(); } } return instance; } }
原理在代码中已经解说得很详细。如果想进一步优化性能,锁定部分可以如下优化
if (uniqueInstance == null) { lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } }
单例模式属于很简单的设计模式,在微软的企业库中有单例模式的实现,微软企业库提供了完善的文档及源码,大家可以参考。