Singleton 单件(创建型模式)
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个类例?这应该是类的设计者的责任,而不是类的使用者。
二、意图(Intent)
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
三、C#语言实现
1)单线程Singleton模式实现
public class Singleton
{
private static Singleton instance = null;
private Singleton(){}
public static Singleton Instance
{
get
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
注:类的默认构造器为public修饰,.Net 2.0设置为静态类则表明其构造器为private修饰。Singleton模式一般不要支持ICloneable克隆接口和不要支持序列化,因为这样有可能导致多个对象实例。在多线程环境下,仍有可能得到多个实例。原因在于if语句,因为一个if语句编译后将得到多条指令,若多个线程同时执行到if语句块指令中且instance为Null,则每个线程将会得到了一个实例。
2)多线程Singleton模式实现
例1:
public class Singleton
{
//volatile修饰符:保证编译器不会对指令顺序进行微调
private static volatile Singleton instance = null;
//辅助器
private static object lockHelp = new object();
private Singleton(){}
public static Singleton Instance
{
get
{
if(instance == null)
{
lock(lockHelper)
{
if(instance == null) //double check
{
instance = new Singleton();
}
}
}
return instance;
}
}
}
例2:
class Singleton
{
public static readonly Singleton Instance = new Singleton(); //内联初始化
private Singleton(){}
}
等效于下面的实现,其实两种方式编译后的中间代码是一至的。
class Singleton
{
public static readonly Singleton Instance;
static Singleton() //静态构造器
{
Instance = new Singleton();
}
private Singleton(){}
}
这里主要了解静态构造器的执行时间只在静态字段初绐化之前初始化。
.Net类型初始化机制保证了这种方式的多线程Singleton模式:
A、访问实例成员之前执行静态构造器
B、实现了晚(缓式)加载,即调用Instance时才创建此类的对象(调用静态构造器)并不是系统一运行就创建了并一直驻留内存中,但是是程序员不可控制的创建过程。
C、多线程环境下也能正常工作,是因为.Net机制已经保证静态构造器只会有一个线程执行。用ildasm反编译后可以看到有这么一个特性beforefieldinit。
也就是说这种方式在.Net平台下是最优的多线程Singleton实例模式,但它也有一点不足之处:不支持参数化的构造器操作。因为私有构造器一个类只有一个且是无参数的。因为私有构造器不允许外界环境(仅供内部隐式调用)调用,所以外部参数无法传到构造器当中。不过如果参数不需要在构造前初始化,则可以通过对象的属性方式赋值。
例3:
public class Singleton
{
private Singleton(){}
public static Singleton Instance
{
get{ return LastLoad.instance }
}
class LastLoad
{
internal static readonly Singleton instance = new Singleton();
static LastLoad(){}
}
}
这是延迟加载的一个最好实现,其初始化过程是程序员可控制的。
思考:
public class Singleton
{
private static Singleton instance = null;
private Singleton(int x,int y){this.x = x;this.y=y;}
public static Singleton GetInstance(int x,int y)
{
if(instance == null)
{
instance = new Singleton(x,y);
}
// else
// {
// this.x = x;
// this.y = y;
// }
return instance;
}
int x;
int y;
public int Add()
{
return x + y;
}
}
class Test
{
public static void Main()
{
Singleton s1 = Singleton.GetInstance(100,200);
Console.WriteLine(s1.Add());
Singleton s2 = Singleton.GetInstance(200,300);
Console.WriteLine(s2.Add());
Console.WriteLine(object.ReferenceEquals(s1,s2) == true);
}
}
注释else中的代码与不注释else中的代码输出的结果分别是什么?
四、扩展
将一个实例扩展到n个实例。例如对象池的实现。
五、总结
也许有些朋友会认为Singleton模式没什么作用,直接用静态字段或属性就可以达到全局唯一的目的,其实不然,因为多线程环境下静态字段并不能确保唯一,也就是有单线程下的同样问题和if语句一样。
readonly关键字确保客户程序不将null等不合理的值赋给Instance字段。也就是客户程序只能读,不能写。
获得类的实例个数,可以在实例构造器中累加一静态计算器字段。
Singleton模式应该说是《设计模式》一书中32种设计模式最简单的一种模式,但它里面包含的东西其实并不简单。以上也是Singleton模式的一个演化过程,也许以后在别的语言当中Singleton模式将变得会更简单。每种模式本身都是总结、演化得到。
参考文献:
GOF《设计模式:可复用面向对象软件的基础》
《C#设计模式》
李建忠《设计模式纵横谈》
Terrylee的深入浅出单件模式(Sigleton Pattern)