说到Singleton,就不能不提全局变量。过去我们反对全局变量,为什么呢?全局变量一般存在三个问题:
1. 命名冲突,也叫名字污染
2. 初始化顺序依赖问题。
3. 远程代码耦合问题。
对于第一个问题,其实解决很简单,通过命名规范可以有效解决。另外,借助语言机制也容易解决,例如C++的namespace, class或struct的静态数据成员。Java和C#根本不允许全局变量,并且引入包机制,都可以缓解甚至消除这一影响,至少,Singleton对该问题的解决力度并不超过这些语言提供的内建机制。
初始化顺序依赖问题是一个相当微妙的问题。借助于Singleton, 我们可以强制某些对象按需创建,避免初始化顺序依赖导致的问题。然而,初始化顺序是一个雷区,我们必须小心翼翼地绕过去,但首先不应该通过实现技术来规避问题,而应该调整设计,让初始化顺序问题根本不出现才是上策。第二,某些情况下,我们确实需要规避初始化顺序问题,我们也需要清醒地认识到该实现手段影响到的代码范围,受其影响的部分越单一越好。
远程代码耦合,这是我们反对全局变量的核心问题。任何长距离的耦合都将导致代码的可读性下降,进而可能影响代码结构,导致结构僵化,无力应对因需求变化导致的结构调整。很遗憾,Singleton并不能对这个问题有任何帮助。当我们回避全局变量的时候,事实上,我们也规避了上述的三个问题。可是,如果我们不能审慎地使用Singleton就会重新落入全局变量的核心陷阱中去。
然而,Singleton确实是运用的最为广泛的模式之一,我的第一个模式的尝试也是从Singleton开始的。这只能归功于这一模式概念上的简单性和实现上的方便性:一个变形的全局变量—类静态变量,函数局部静态变量--就可以成为该模式的实现。再加上模式传授的困难性,简单性和误解,导致了该模式的滥用。
事实上,对于许多程序员来说,都不能熟练运用设计模式。往往是一些资深的程序员会首先尝试使用,然而,即便是资深的程序员,对模式的运用常常也是不正确的。由于资深程序员的权威性,导致了这些错误的模式运用得以进一步扩散。
除了前述的3个问题外,在该模式的实现上也存在许多问题。典型的例子包括:类静态成员实现,该实现导致并不能解决问题2。动态创建,却没有实现释放对象的机制。对于多线程环境,没有考虑安全性问题。当然,并非所有环境都需要解决这些问题,但是这些问题应该是被考虑过的。
什么是Singleton适用的时候?没有确定的答案。从我的经验来说,有一些方法,可以帮助我们。例如,尽量避免函数有状态,宁可要函数状态和对象有关而不要和全局对象有关。无状态的函数是好的,而不恰当的Singleton则会导致某些函数有状态。
我会采用Singleton的一般是和静态类型信息关联的地方,例如,需要向某个注册表中注册许多类的创建器,这个注册表可以实现为Singleton,然后把注册过程利用局部静态对象的构造函数来自动实现。这个注册过程是个单一的过程。另外,Singleton作用于静态信息,需要变化的风险大大降低。不过,我似乎还从未依靠静态变量解决过初始化依赖顺序问题。但是,初始化依赖顺序不是一定可以回避的,这时候使用Singleton也是合理的。
而通常Singleton的误用则有:用于程序运行的环境变量,资源管理器,特殊的资源,会话,数据库连接等等。这些内容没有必要设计成Singleton,如果程序扩展,你可以应付多会话,多数据库连接等等问题。一旦使用了Singleton,所有这些扩展都将成为噩梦。现实是,一个有价值的程序,总是会被要求应付更复杂的环境。
<iframe frameborder="0" id="gn_notemagic" style="position: absolute; display: block; opacity: 0.7; z-index: 500; width: 18px; height: 22px; top: 152px; right: 160px;" src="http://www.google.com/gn/static_files/blank.html"></iframe>