单例模式(Singleton)是一种喜闻乐见非常常用的设计模式。老师说,架构师(或者主程)也说“要面向对象”,“不能使用全局变量”,当然这些我们也都知道,那么当我们真的真的很需要使用类似于全局变量的东西的时候该怎么办?单例模式为我们提供了一种解决方案。
那么具体要求是什么样的呢?简而言之,在程序运行期间,单例类的实例只能有一个(或没有)。恩?说好的是类似于全局变量的东西呢?当然为了能在程序任何地方调用它,需要为它实现一个静态方法(例如GetInstance),通过这个方法可以获得单例类的唯一实例。
废话不多说直接上C#代码:
public class Singleton
{
static Singleton instance;
static readonly object syncObj = new object();
private Singleton()
{
}
public static Singleton GetInstance()
{
if (instance == null) {
lock (syncObj) {
if (instance == null) {
instance = new Singleton ();
}
}
}
return instance;
}
public static void PurgeInstance()
{
instance = null;
}
}
这就是广为流传的线程安全的懒汉型单例。
静态Singleton类型的instance变量便是Singleton的实例,因为是static的变量,所以可以保证实例只会有一个。
为了保证实例唯一,还要把构造函数的访问修饰符设置为protected,这样外部就无法调用new Singleton()来创建实例了。
为了保证线程安全,我们新建了一个只读的object作为锁,并在上下分别加了instance == null的判断。第一个判断是为了防止线程每一次都要加锁,第二个判断是为了让等待中的线程进入代码片之后不会再重新创建单例。
有时候我们可能需要重建单例,那么PurgeInstance方法就为这项操作提供了前提。
或许有人就会发现一个问题,每次添加一个单例类的时候,都需要把上面的那些代码重新写一遍,虽然复制、粘贴、查找、替换这些操作也不算太复杂。但是重复的劳动总归不美不科学。这位朋友,你幸运了,正巧C#有一种叫做泛型的东西。我们来看看怎么实现:
public class TSingleton<T> where T : class, new() {
static T instance;
static readonly object syncObj = new object();
public static T GetInstance()
{
if (instance == null) {
lock (syncObj) {
if (instance == null) {
instance = new T ();
}
}
}
return instance;
}
public static void PurgeInstance()
{
instance = null;
}
}
用法:
public class MySingleton: TSingleton<MySingleton>
{
}
最大的缺陷在于构造函数必须是public,这样便无法保证单例的唯一性,只是保证了全局访问。
写在后面:
单例模式是一个非常好用的模式,一般会用在管理类或者程序里只有唯一实例的类。例如一个公司的CEO,就可视作单例(虽然对底层码农来讲可能并不是全局访问)。再比如,一个游戏里的资源管理类,我可能需要随时访问它,并且不希望每次加载图片的时候都要新建一个资源管理类的实例,那么单例就是你的可选项之一(或者静态类,各有千秋)。
但是单例多了,可能并不是一个好事,一方面,代码可读性会变差,另一方面,如果一个单例类里的数据对外是可以被修改的,那么它就会具有跟全局变量一样的危害(全局可写会导致不可预知的错误,并且调试起来会非常困难)。