单件模式可以保证一个类有且只有一个实例,并且提供一个访问他的全局访问点。在实际应用中有很多这样的情况,例如:窗口管理器,打印机或者一个COM1实例。
创建单件的方法
让一个类只有一个实例有很多方法,最容易的方法是在类中设置一个静态变量,并且提供一个得到该实例对象的方法,在第一次实例对象的时候初始静态变量,以后再次实例化该类时都要判断是否初始化,因为静态变量只能有一个实例。为了防止多次实例化,把类的构造函数设置为私有属性。看下面的例子:
{
private static bool instance_flag = false; //静态变量,标识是否实例化该类
private Spooler()
{ }
/// <summary>
/// 提供静态获取该类对象的方法
/// 如果已经有一个实例,再次请求返回null
/// </summary>
/// <returns></returns>
public static Spooler GetSpooler()
{
if (!instance_flag)
{
return new Spooler();
}
else
{
return null;
}
}
public void Something()
{ }
}
这样做的好处是不用考虑异常,如果已经实例化过,返回一个空值而已。如果尝试直接创建对象编译器会提示构造函数私有,编译失败。调用就简单了
spooler.Something();
Spooler spooler2=new Spooler(); // 提升错误:不可访问,因为它受保护级别限制//
如果需要实例化两个或者三个实例,修改静态变量值为整数,作为计数器可以了。
异常与实例
上面的方法虽然可以实现单件模式,但有个缺点:需要程序员自己判断返回值是否为空。从程序设计角度来讲,这种情况应该尽量避免! 我们可以换一种方法来提示程序员已经实例过该对象。创建一个异常类,当多次实例化该类时,抛出一个异常,提升程序员采取行动。
{
private static bool instance_flag = false; //静态变量,标识是否实例化该类
private Spooler()
{ }
/// <summary>
/// 提供静态获取该类对象的方法
/// 如果已经有一个实例,再次请求返回null
/// </summary>
/// <returns></returns>
public static Spooler GetSpooler()
{
if (!instance_flag)
{
return new Spooler();
}
else
{
throw new SingletonException("只能有一个实例");
}
}
public void Something()
{ }
}
public class SingletonException : Exception
{
public SingletonException(string s) : base(s) {
}
}
这样会等到我们想要的效果了。
还有一些补充
下面引用TerryLee写的关于线程安全方面一些补充,整理到一起方面学习。
上面的写法不是线程安全的,当两个线程同时访问GetSpooler()方法,会得到两个Spooler实例,这样违背了我们的设计原则。可以用C#锁机制保证线程安全。
{
private static readonly object locker = new object();
private static bool instance_flag = false; //静态变量,标识是否实例化该类
private Spooler()
{ }
public static Spooler GetSpooler()
{
lock (locker) //锁定,保证线程安全
{
if (!instance_flag)
{
return new Spooler();
}
else
{
throw new SingletonException("只能有一个实例");
}
}
}
}
这种方式可以保证线程安全,因为当多个线程同时访问加锁对象时,只有一个线程可以进入。但是这种实现方式增加了额外的开销,损失了性能。
双重锁定
{
if (!instance_flag)
{
lock (locker)
{
if (!instance_flag)
{
return new Spooler();
}
else
{
throw new SingletonException("只能有一个实例");
}
}
}
else
throw new SingletonException("只能有一个实例");
}
这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建时它才加锁,提高性能。不过对于这种情况我们有更好的解决方案。
静态初始化
{
static readonly Spooler instance = new Spooler(); //线程安全
static Spooler()
{
}
Spooler()
{ }
public static Spooler GetSpooler()
{
return instance;
}
}
对于该写法特点摘用TerryLee的原话:
延迟初始化
{
Spooler()
{ }
public static Spooler Instance()
{
return Nested._instance;
}
public static string getStr() //不会初始化Spooler类
{
return ..............;
}
class Nested //所有初始化都在这儿
{
static Nested()
{ }
internal static readonly Spooler _instance = new Spooler();
}
}