整理一份公司培训时候的幻灯片,这是由我所作,关于设计模式中单例模式以及C#编程语言中的使用情况,您也可以直接下载PDF版本。
下面做一些简要的说明。
单例模式(Singleton Pattern),又称作单件模式,当然也有一种诙谐的称谓:单身模式。在经典的GoF所著的《Design Patterns》一书中,对单例模式有着详尽的介绍,这本书网上有全文版本。
单例模式的意图是保证一个类仅有一个实例,并且要提供一个全局访问点来访问这个实例。通常这个全局访问点是一个静态方法或者C#中的一个属性。
在C#中,典型的单例模式实现方法可以如下:
public class Manager
{
private static Manager Mgr;
//constructor must be private
private Manager() { }
//public and static, or you can change it to Property instead of Method.
public static Manager GetInstance()
{
if (Mgr == null)
{
Mgr = new Manager();
}
return Mgr;
}
}
当外部需要Manager
的实例时,可以调用GetInstance()
这个静态方法。由于Manager
类的构造器是私有的,这也就避免了其他方式实例化这个Manager
类。GetInstance()
方法内部的实现,保证了全局中只有一个Manager
实例。
问题肯定不会这么简单就被解决,比如在多线程环境中,上述代码就会有很大的隐患。
有一种情况很常见:两个线程同时调用
GetInstance()
方法; 当某一个线程由于Mgr
为null
而进入条件判断代码块的时候,而恰恰还没有执行实例化一个Manager
对象,这时候另一个线程由于Mgr
为null
,所以也会进入这个条件语句中
上面两种情况,很显然都会创建Manager
实例,这也就违背了单例模式的意图了。
利用C#的特性,我们可以把一个线程先锁住,等到这个线程完成后,再让下一个线程访问GetInstance()
方法:
private static readonly object syncObject = new object();
//public and static, or you can change it to Property instead of Method.
public static Manager GetInstance()
{
//double check
if (Mgr == null)
{
lock (syncObject)
{
if (Mgr == null)
{
Mgr = new Manager();
}
}
}
return Mgr;
}
代码中用到了双重检查锁定(double check locking)的技术,是为了提高性能考虑,因为C#中lock
语句是很耗性能的。第一道检查,是基于如果Mgr
不为null
的时候就不需要lock
了,提高性能。第二道检查,是基于两个线程同时通过第一道检查后,第一个线程解锁后,由于Mgr
此时已经不为null
,所以第二个线程就不用实例化Manager
了。
单例模式有两种实现方式,主要基于构建的方式不同:
- 延迟初始化(Lazy Initialization),也叫“懒汉模式”:单例实例在第一次使用时被构建;
- 热初始化(Eager Initialization),也叫“恶汉模式”:单例实例在类加载时创建
前面创建单例模式的方式都属于延迟初始化。.NET 4.0以后提供了一个
Lazy<T>
泛型类,可以被应用于这个场景,省却代码的编写量。
public class Manager
{
private static Lazy<Manager> mgr = new Lazy<Manager>();
//version of Thread Safe
//private static Lazy<Manager> mgr = new Lazy<Manager>(true);
private Manager() { }
public static Manager GetInstance()
{
return mgr.Value;
}
}
Lazy<T>
的构造器重载版本可以帮我们解决多线程的问题。
C#使用静态初始化来完成单例模式中的热初始化。需要注意的是,不需要考虑多线程的问题,因为CLR会自动解决多线程同步的问题。如果程序经常要用到这个实例,运用热初始化可以显著提高性能。
public class Manager
{
private static readonly Manager mgr = new Manager();
private Manager() { }
public static Manager GetInstance()
{
return mgr;
}
}
StackOverflow中对于单例模式都是持否定态度的,比如这个:
In theory: when you need to restrict the instantiation of an object to one instance. In practice: never.
主要基于下面几个原因:
- 违反了单一职责原则
- 耦合度过大
- 单元测试基本无法进行
- 开发混淆,造成混乱。比如作为API提供的时候。
总之,
There‘s at most a can use but there no need.