Java模式设计之单例模式(一)
副标题#e#
作为工具的建设模式[GOF95], 单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。这个类称为单例类。
注:本文乃阎宏博士的《Java与模式》一书的第十五章。
引言
单例模式的要点
单例单例
显然单例模式的要点有三个;一是某种种只能有一个实例;二是它必需自行建设这个事例;三是它必需自行向整个系统提供这个实例。在下面的工具图中,有一个"单例工具",而"客户甲"、"客户乙" 和"客户丙"是单例工具的三个客户工具。可以看到,所有的客户工具共享一个单例工具。并且从单例工具到自身的毗连线可以看出,单例工具持有对本身的引用。
资源打点
一些资源打点器经常设计成单例模式。
在计较机系统中,需要打点的资源包罗软件外部资源,譬如每台计较机可以有若干个打印机,但只能有一个Printer Spooler, 以制止两个打印功课同时输出到打印机中。每台计较机可以有若干传真卡,可是只应该有一个软件认真打点传真卡,以制止呈现两份传真功课同时传到传真卡中的环境。每台计较机可以有若干通信端口,系统该当会合打点这些通信端口,以制止一个通信端口同时被两个请求同时挪用。
需要打点的资源包罗软件内部资源,譬如,大大都的软件都有一个(甚至多个)属性(properties)文件存放系统设置。这样的系统该当由一个工具来打点一个属性文件。
需要打点的软件内部资源也包罗譬如认真记录网站来访人数的部件,记录软件系统内部事件、堕落信息的部件,或是对系统的表示举办查抄的部件等。这些部件都必需会合打点,不行政出多头。
这些资源打点器构件必需只有一个实例,这是其一;它们必需自行初始化,这是其二;答允整个系统会见本身这是其三。因此,它们都满意单例模式的条件,是单例模式的应用。
一个例子:Windows 接纳站
Windows 9x 今后的视窗系统中都有一个接纳站,下图就显示了Windows 2000 的接纳站。
在整个视窗系统中,接纳站只能有一个实例,整个系统都利用这个惟一的实例,并且接纳站自行提供本身的实例。因此,接纳站是单例模式的应用。
#p#副标题#e#
双重查抄成例
在本章最后的附录里研究了双重查抄成例。双重查抄成例与单例模式并无直接的干系,可是由于许多C 语言设计师在单例模式内里利用双重查抄成例,所以这一做法也被许多Java 设计师所仿照。因此,本书在附录里提醒读者,双重查抄成例在Java 语言里并不能创立,详情请见本章的附录。
单例模式的布局
单例模式有以下的特点:
.. 单例类只可有一个实例。
.. 单例类必需本身建设本身这惟一的实例。
.. 单例类必需给所有其他工具提供这一实例。
固然单例模式中的单例类被限定只能有一个实例,可是单例模式和单例类可以很容易被推广到任意且有限多个实例的环境,这时候称它为多例模式(Multiton Pattern) 和多例类(Multiton Class),请见"专题:多例(Multiton )模式与多语言支持"一章。单例类的大略类图如下所示。
由于Java 语言的特点,使得单例模式在Java 语言的实现上有本身的特点。这些特点主要表示在单例类如何将本身实例化上。
饿汉式单例类饿汉式单例类是在Java 语言里实现得最为轻便的单例类,下面所示的类图描写了一个饿汉式单例类的典范实现。
从图中可以看出,此类已经自已将本身实例化。
代码清单1:饿汉式单例类
public class EagerSingleton
{
private static final EagerSingleton m_instance =
new EagerSingleton();
/**
* 私有的默认结构子
*/
private EagerSingleton() { }
/**
* 静态工场要领
*/
public static EagerSingleton getInstance()
{
·224·Java 与模式
return m_instance;
}
}
读者可以看出,在这个类被加载时,静态变量m_instance 会被初始化,此时类的私有结构子会被挪用。这时候,单例类的惟一实例就被建设出来了。
Java 语言中单例类的一个最重要的特点是类的结构子是私有的,从而制止外界操作结构子直接建设出任意多的实例。值得指出的是,由于结构子是私有的,因此,此类不能被担任。
懒汉式单例类
#p#分页标题#e#
与饿汉式单例类沟通之处是,类的结构子是私有的。与饿汉式单例类差异的是,懒汉式单例类在第一次被引用时将本身实例化。假如加载器是静态的,那么在懒汉式单例类被加载时不会将本身实例化。如下图所示,类图中给出了一个典范的饿汉式单例类实现。
代码清单2:懒汉式单例类
package com.javapatterns.singleton.demos;
public 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 语言,并将它本身实例化的方法从懒汉式改为饿汉式。只是它的子类实例化的方法只能是懒汉式的, 这是无法改变的。如下图所示是挂号式单例类的一个例子,图中的干系线表白,此类已将本身实例化。
代码清单3:挂号式单例类
import java.util.HashMap;
public class RegSingleton
{
static private HashMap m_registry = new HashMap();
static
{
RegSingleton x = new RegSingleton();
m_registry.put( x.getClass().getName() , x);
}
/**
* 掩护的默认结构子
*/
protected RegSingleton() {}
/**
* 静态工场要领,返还此类惟一的实例
*/
static public RegSingleton getInstance(String name)
{
if (name == null)
{
name = "com.javapatterns.singleton.demos.RegSingleton";
}
if (m_registry.get(name) == null)
{
try
{
m_registry.put( name,
Class.forName(name).newInstance() ) ;
}
catch(Exception e)
{
System.out.println("Error happened.");
}
}
return (RegSingleton) (m_registry.get(name) );
}
/**
* 一个示意性的贸易要领
*/
public String about()
{
return "Hello, I am RegSingleton.";
}
}
它的子类RegSingletonChild 需要父类的辅佐才气实例化。下图所示是挂号式单例类子类的一个例子。图中的干系表白,此类是由父类将子类实例化的。
下面是子类的源代码。
代码清单4:挂号式单例类的子类
import java.util.HashMap;
public class RegSingletonChild extends RegSingleton
{
public RegSingletonChild() {}
/**
* 静态工场要领
*/
static public RegSingletonChild getInstance()
{
return (RegSingletonChild)
RegSingleton.getInstance(
"com.javapatterns.singleton.demos.RegSingletonChild" );
}
/**
* 一个示意性的贸易要领
*/
public String about()
{
return "Hello, I am RegSingletonChild.";
}
}
#p#分页标题#e#
在GoF 原始的例子中,并没有getInstance() 要领,这样获得子类必需挪用的getInstance(String name)要领并传入子类的名字,因此很不利便。本章在挂号式单例类子类的例子里,插手了getInstance() 要领,这样做的长处是RegSingletonChild 可以通过这个要领,返还自已的实例。而这样做的缺点是,由于数据范例差异,无法在RegSingleton 提供这样一个要领。由于子类必需答允父类以结构子挪用发生实例,因此,它的结构子必需是果真的。这样一来,就便是答允了以这样方法发生实例而不在父类的挂号中。这是挂号式单例类的一个缺点。
GoF 曾指出,由于父类的实例必需存在才大概有子类的实例,这在有些环境下是一个挥霍。这是挂号式单例类的另一个缺点。