本文PDF下载(转载请注明出处)
定义:单例模式(Singleton):也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
——来自《维基百科》
通常在需要将某类型单例化的时候,我们都要为它设置一个静态类型引用,一个静态获取实例的方法或者属性,还要将默认构造私有化。现在,我们来动手简化一下这种重复性劳动。首先,我们先来温习一下最常见的单例模式的具体实现:
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
那么,我们每次要对一个类型进行单例化改造的时候,以下几个步骤是无可避免的:
- 创建私有的静态的本类型引用。
- 默认构造私有化。
- 创建公共的静态的本类型对象获取函数。
- 在函数内判断instance是否应该执行实例化操作。
常年的Copy- Paste是否令你感到厌倦?好的,下面的示例将把单例模式的行为使用泛型类集中完成:
public class SingleObject<T> where T : new() { // 类型T的唯一实例 private static T instance; // 用于lock块的对象 private static readonly object sync = new object(); // 私有的默认构造 private SingleObject() { } /// <summary> /// 获取类型T的唯一实例 /// </summary> public static T Value { get { if (instance == null) { lock (sync) { if (instance == null) { instance = new T(); //测试用,如果T类型创建了实例,则输出它的类型名称 Console.WriteLine("{0}:创建了对象",typeof(T).Name); } } } return instance; } } }
在代码中,SingleObject类被定义为“public class SingleObject<T> where T : new()”,他是一个泛型类,并且类型参数T被约束为必须有公共的无参数构造函数。这样,就可以在类内部调用new T()来创建它的对象了。
其次,定义了private static T instance,用来保存类型参数T指定的类型的唯一实例。
再次,定义了private static readonly object sync = new object(),用来完成双检锁模式。这是因为如果单例模式处于多线程环境中,需要用这个模式来保证不会因为并发而创建一个以上的对象。
再往下,将默认构造方法设置为private,这样外部就不能以关键字new来创建对象。
最后,定义了public static T Value,用于外部调用类型参数T的唯一实例。这里用到了双检锁模式。我在创建对象的位置加入了测试数据,输出T的类型名称。下面是调用代码:
SingleObject<Person>.Value.Name = "张三"; Console.WriteLine(SingleObject<Person>.Value.Name); SingleObject<Person>.Value.Name = "李四"; Console.WriteLine(SingleObject<Person>.Value.Name);
运行上述代码,输出结果如下:
至此,可以看出SingleObject<T>可以实现单例模式。其实更准确的说,是在使用SingleObject<T>来维护一个类型的时候,T类型是单例的,毕竟T类型可以使用关键字new来创建对象。不过,SingleObject<T>确实能节省大量的代码量,具体使用方式大家共同摸索吧。