作为对象的创建模式[GOF] 单例模式确保某一个类只有一个实例而且自行实例化并向整个系统提供这个实例这个类称为单例类
单例模式的要点
单例单例
显然单例模式的要点有三个一是某各类只能有一个实例二是它必须自行创建这个事例三是它必须自行向整个系统提供这个实例在下面的对象图中有一个单例对象而客户甲客户乙 和客户丙是单例对象的三个客户对象可以看到所有的客户对象共享一个单例对象而且从单例对象到自身的连接线可以看出单例对象持有对自己的引用
资源管理
一些资源管理器常常设计成单例模式
在计算机系统中需要管理的资源包括软件外部资源譬如每台计算机可以有若干个打印机但只能有一个Printer Spooler 以避免两个打印作业同时输出到打印机中每台计算机可以有若干传真卡但是只应该有一个软件负责管理传真卡以避免出现两份传真作业同时传到传真卡中的情况每台计算机可以有若干通信端口系统应当集中管理这些通信端口以避免一个通信端口同时被两个请求同时调用
需要管理的资源包括软件内部资源譬如大多数的软件都有一个(甚至多个)属性(properties)文件存放系统配置这样的系统应当由一个对象来管理一个属性文件
需要管理的软件内部资源也包括譬如负责记录网站来访人数的部件记录软件系统内部事件出错信息的部件或是对系统的表现进行检查的部件等这些部件都必须集中管理不可政出多头
这些资源管理器构件必须只有一个实例这是其一它们必须自行初始化这是其二允许整个系统访问自己这是其三因此它们都满足单例模式的条件是单例模式的应用
一个例子Windows 回收站
Windows x 以后的视窗系统中都有一个回收站下图就显示了Windows 的回收站
在整个视窗系统中回收站只能有一个实例整个系统都使用这个惟一的实例而且回收站自行提供自己的实例因此回收站是单例模式的应用
双重检查成例
在本章最后的附录里研究了双重检查成例双重检查成例与单例模式并无直接的关系但是由于很多C 语言设计师在单例模式里面使用双重检查成例所以这一做法也被很多Java 设计师所模仿因此本书在附录里提醒读者双重检查成例在Java 语言里并不能成立详情请见本章的附录
单例模式的结构
单例模式有以下的特点
…… 单例类只可有一个实例
…… 单例类必须自己创建自己这惟一的实例
…… 单例类必须给所有其他对象提供这一实例
虽然单例模式中的单例类被限定只能有一个实例但是单例模式和单例类可以很容易被推广到任意且有限多个实例的情况这时候称它为多例模式(Multiton Pattern) 和多例类(Multiton Class)请见专题多例(Multiton )模式与多语言支持一章单例类的简略类图如下所示
由于Java 语言的特点使得单例模式在Java 语言的实现上有自己的特点这些特点主要表现在单例类如何将自己实例化上
饿汉式单例类饿汉式单例类是在Java 语言里实现得最为简便的单例类下面所示的类图描述了一个饿汉式单例类的典型实现
从图中可以看出此类已经自已将自己实例化
代码清单饿汉式单例类
public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton()/** * 私有的默认构造子*/ private EagerSingleton() { } /** * 静态工厂方法*/ public static EagerSingleton getInstance()
{
Java 与模式return m_instance}
读者可以看出在这个类被加载时静态变量m_instance 会被初始化此时类的私有构造子会被调用这时候单例类的惟一实例就被创建出来了
Java 语言中单例类的一个最重要的特点是类的构造子是私有的从而避免外界利用构造子直接创建出任意多的实例值得指出的是由于构造子是私有的因此此类不能被继承
懒汉式单例类
与饿汉式单例类相同之处是类的构造子是私有的与饿汉式单例类不同的是懒汉式单例类在第一次被引用时将自己实例化如果加载器是静态的那么在懒汉式单例类被加载时不会将自己实例化如下图所示类图中给出了一个典型的饿汉式单例类实现
代码清单懒汉式单例类
package comjavapatternssingletondemospublic class LazySingleton { private static LazySingleton m_instance = null/** * 私有的默认构造子保证外界无法直接实例化*/ private LazySingleton() { } /** * 静态工厂方法返还此类的惟一实例*/ synchronized public static LazySingleton getInstance()
{ if (m_instance == null)
{ m_instance = new LazySingleton()} return m_instance}
读者可能会注意到在上面给出懒汉式单例类实现里对静态工厂方法使用了同步化以处理多线程环境有些设计师在这里建议使用所谓的双重检查成例必须指出的是双重检查成例不可以在Java 语言中使用不十分熟悉的读者可以看看后面给出的小节
同样由于构造子是私有的因此此类不能被继承饿汉式单例类在自己被加载时就将自己实例化即便加载器是静态的在饿汉式单例类被加载时仍会将自己实例化单从资源利用效率角度来讲这个比懒汉式单例类稍差些
从速度和反应时间角度来讲则比懒汉式单例类稍好些然而懒汉式单例类在实例化时 必须处理好在多个线程同时首次引用此类时的访问限制问题特别是当单例类作为资源控制器在实例化时必然涉及资源初始化而资源初始化很有可能耗费时间这意味着出现多线程同时首次引用此类的机率变得较大
饿汉式单例类可以在Java 语言内实现 但不易在C++ 内实现因为静态初始化在C++ 里没有固定的顺序因而静态的m_instance 变量的初始化与类的加载顺序没有保证可能会出问题这就是为什么GoF 在提出单例类的概念时举的例子是懒汉式的他们的书影响之大以致Java 语言中单例类的例子也大多是懒汉式的实际上本书认为饿汉式单例类更符合Java 语言本身的特点
登记式单例类
登记式单例类是GoF 为了克服饿汉式单例类及懒汉式单例类均不可继承的缺点而设计的本书把他们的例子翻译为Java 语言并将它自己实例化的方式从懒汉式改为饿汉式只是它的子类实例化的方式只能是懒汉式的 这是无法改变的如下图所示是登记式单例类的一个例子图中的关系线表明此类已将自己实例化
代码清单登记式单例类
import javautilHashMappublic class RegSingleton { static private HashMap m_registry = new HashMap()static { RegSingleton x = new RegSingleton()m_registryput( xgetClass()getName() x)} /** * 保护的默认构造子*/ protected RegSingleton() {} /** * 静态工厂方法返还此类惟一的实例*/ static public RegSingleton getInstance(String name)
{ if (name == null)
{ name = comjavapatternssingletondemosRegSingleton} if (m_registryget(name) == null)
{ try { m_registryput( nameClassforName(name)newInstance() ) } catch(Exception e)
{ Systemoutprintln(Error happened)} return (RegSingleton) (m_registryget(name) )} /** * 一个示意性的商业方法*/ public String about()
{ return Hello I am RegSingleton}它的子类RegSingletonChild 需要父类的帮助才能实例化下图所示是登记式单例类子类的一个例子图中的关系表明此类是由父类将子类实例化的
下面是子类的源代码
代码清单登记式单例类的子类
import javautilHashMappublic class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 静态工厂方法*/ static public RegSingletonChild getInstance()
{ return (RegSingletonChild)
RegSingletongetInstance(comjavapatternssingletondemosRegSingletonChild )} /** * 一个示意性的商业方法*/ public String about()
{ return Hello I am RegSingletonChild}
在GoF 原始的例子中并没有getInstance() 方法这样得到子类必须调用的getInstance(String name)方法并传入子类的名字因此很不方便本章在登记式单例类子类的例子里加入了getInstance() 方法这样做的好处是RegSingletonChild 可以通过这个方法返还自已的实例而这样做的缺点是由于数据类型不同无法在RegSingleton 提供这样一个方法由于子类必须允许父类以构造子调用产生实例因此它的构造子必须是公开的这样一来就等于允许了以这样方式产生实例而不在父类的登记中这是登记式单例类的一个缺点
GoF 曾指出由于父类的实例必须存在才可能有子类的实例这在有些情况下是一个浪费这是登记式单例类的另一个缺点