动机:在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
意图:可以保证一个类有且只有一个实例,并提供一个访问它的全局访问点。
单线程Singleton模式的几个要点
l Singleton模式中的实例构造器可以设置为protected以允许子类派生。
l Singleton模式一般不要支持ICloneable接口(ICloneable接口支持克隆,即用与现有实例相同的值创建类的新实例),因为这可能会导致多个对象实例。
l Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例。
(http://www.microsoft.com/china/msdn/archives/library/dndotnet/html/objserializ.asp)
l Singleton模式只考虑了对象创建的管理,没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。
l 不能应对多线程环境:在多线程环境下,使用Singleton模式仍然有可能得到Singleton类的多个实例对象。
使用静态方法创建单件:
Spooler.cs代码示例:
namespace GlobalSpooler
{
/// <summary>
/// Summary description for Spooler.
/// </summary>
public class Spooler {
private static bool instance_flag= false;
private Spooler() {
}
public static Spooler getSpooler() {
if (! instance_flag)
return new Spooler ();
else
return null;
}
}
}
ClobSpooler.cs代码示例:
namespace GlobalSpooler
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class GlobSpooler
{
static void Main(string[] args) {
Spooler sp1 = Spooler.getSpooler();
if (sp1 != null)
Console.WriteLine ("Got 1 spooler");
Spooler sp2 = Spooler.getSpooler ();
if (sp2 == null)
Console.WriteLine ("Can't get spooler");
//fails at compile time
//Spooler sp3 = new Spooler ();
}
}
}
让一个类只有一个实例,最容易的方法是在类中嵌入一个静态变量,并在第一个类实例中设置该变量,而且每次进入构造函数都要做检查。不管类有多少个实例,静态变量只能有一个实例。为了防止类被多次实例化,把构造函数声明为私有的,这样只能在类的静态方法里创建一个实例。
优点:如果单件已经存在,不需要考虑异常处理。如需要修改程序,允许该类有两个或三个实例,则修改Spooler类可以很容易实现这一点。
缺点:需要程序员检查getSpooler方法的返回值,以确保它不是空的。让程序员始终记得去检查错误的设想是招致失败的开始,应该尽力避免。
解决方案:创建一个异常类,如果试图多次实例化该类,它回抛出一个异常,这时才需要程序员采取行动,因而这是一个安全的方法。
SingletonException.cs代码示例:
namespace SingleSpooler
{
/// <summary>
/// Summary description for SingletonException.
/// </summary>
public class SingletonException:Exception
{
//new exception type for singleton classes
public SingletonException(string s):base(s) {
}
}
}
如果已创建则抛出异常
Spooler.cs代码示例:
namespace SingleSpooler
{
/// <summary>
/// Prototype of Spooler Singleton
/// such that only one instane can ever exist.
/// </summary>
public class Spooler {
static bool instance_flag = false; //true if one instance
public Spooler() {
if (instance_flag)
throw new SingletonException("Only one printer allowed");
else
instance_flag=true; //set flag for one instance
Console.WriteLine ("printer opened");
}
}
}
创建一个类实例
SingleSpooler.cs代码示例:
namespace SingleSpooler
{
/// <summary>
/// Summary description for Class1.
/// </summary>
public class SingleSpooler {
static void Main(string[] args) {
Spooler pr1, pr2;
//open one printer--this should always work
Console.WriteLine ("Opening one spooler");
try {
pr1 = new Spooler();
}
catch (SingletonException e) {
Console.WriteLine (e.Message);
}
//try to open another printer --should fail
Console.WriteLine ("Opening two spoolers");
try{
pr2 = new Spooler();
}
catch (SingletonException e) {
Console.WriteLine (e.Message);
}
}
}
}
提供一个单件的全局访问点
解决方案(一):
在程序的开头创建单件,并将其作为参数传递到需要使用它的类中,如:
pr1 = iSpooler.Instance();
Customers cust = new Customers(pr1);
此方法的缺点是,在某次程序运行中,可能不需要所有的单件,这样会影响程序的性能。
解决方案(二):
在程序创建一个所有单件类的注册表,并使注册表始终是可用的。每次实例化一个单件,都将其记录在注册表中。程序的任何部分都能使用标识符串访问任何一个单件实例,并能取回相应的实例变量。
此方法的缺点是:减少了类型检查,因为注册表中的单件表可能把所有的单件都保存成对象类型。另外,注册表本身也可能是一个单件,必须使用构造函数或其他set函数把它传递给程序的所有部分。
提供一个全局访问点的最常用方式是使用类的静态方法。类名始终是可用的,静态方法只能由类调用,不能由类的实例调用,所以,不管程序中有多少个地方调用该方法,永远只能有一个这样的类实例。
单件模式的其他效果
1. 子类花一个单件很难,因为只有在基类单件没有被实例化时,才能实现这一点。
2. 可以很容易修改一个单件,使它有少数几个实例,这样做是允许的而且是有意义的。