图4. 登记式单态类的一个例子 图中的关系线表明,此类自已将自己实例化。 package com.javapatterns.singleton.demos; import java.util.HashMap; public class RegSingleton { 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) ); } static private HashMap m_registry = new HashMap(); static { RegSingleton x = new RegSingleton(); m_registry.put( x.getClass().getName() , x); } public String about() { return "Hello, I am RegSingleton."; } } 代码清单4. 登记式单态类。(注重为简单起见,这里没有考虑多线程访问限制的问题,读者可自行加入一个有双重 检查的访问限制) 它的子类
图5. 登记式单态类子类的一个例子。 图中的关系线表明,此类是由父类将自己实例化的。 package com.javapatterns.singleton.demos; 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."; } } 代码清单5. 登记式单态类的子类。 在GoF原始的例子中,并没有getInstance()方法,这样得到子类必须调用文类的getInstance(String name) 方法,并传入子类的名字,很不方便。 作者在登记式单态类子类的例子里,加入了getInstance()方法,这样做的好处是RegSingletonChild 可以通过这个方法,返还自已的实例,而这样做的缺点是,由于数据类型不同,无法在RegSingleton提供 这样一个方法。 由于子类必须充许父类以构造子调用产生实例,因此它的构造子必须是公开的。这样一来,就等于答应了 以这样方式产生实例而不在父类的登记中。这是登记式单态类的一个缺点。 GoF曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。 这是登记式单态类的另一个缺点。 爪哇语言里的垃圾回收 爪哇语言里垃圾回收使得单态类的使用变得有点复杂。原因就在于JDK1.1版里加进去的类的自动清除。 这种类的垃圾回收会清除掉类本身,而不仅仅是对象!事实上JDK1.1甚至可以清除掉一些系统类! 在JDK1.0.x版本里,类的自动清除尚未加入。 在JDK1.2及以后的版本里,升阳公司又收紧了类的垃圾回收规则,它规定,所有通过局部的和系统的 类加载器加载的类,永不被回收。并且,通过其它类加载器加载的类,只有在加载器自己被回收后才可被回收。 在1.1版JDK里使用单态类的读者,假如不了解这一版爪哇语言的特点,很有可能会碰到类消失掉的奇异问题。 为了使你的单态类能在所有版本的爪哇环境里使用,作者非凡提供一个"看管"类程序,它能保证你的单态类, 甚至其它任何对象,一旦交给"看管"对象,即不会莫名其妙地被垃圾回收器回收,直到你把它从"看管" 那里把它释放出来。
图6. "看管"类的一个例子 package com.javapatterns.singleton.demos; import java.util.Vector; /** * This class keeps your objects from garbage collected */ public class ObjectKeeper extends Thread { private ObjectKeeper() { new Thread(this).start(); } public void run() { try { join(); } catch (InterruptedException e) {} } /** * Any object passed here will be kept until you call discardObject() */ public static void keepObject(Object myObject) { System.out.println(" Total number of kept objects: " + m_keptObjects.size()); m_keptObjects.add(myObject); System.out.println(" Total number of kept objects: " + m_keptObjects.size()); } /** * This method will remove the protect of the object you pass in and make it * available for Garbage Collector to collect. */ public static void discardObject(Object myObject) { System.out.println(" Total number of kept objects: " + m_keptObjects.size()); m_keptObjects.remove(myObject); System.out.println(" Total number of kept objects: " + m_keptObjects.size()); } private static ObjectKeeper m_keeper = new ObjectKeeper(); private static Vector m_keptObjects = new Vector(); } 代码清单6. 看管类的一个实现。 看管类应当自我实例化,而且在每个系统里只需一个实例。这就意味着看管类本身就应当是单态类。当然,类 消失的事情绝不可以发生在它自己身上。作者提供的例子刚好满足所有的要求。 一个实用的例子 这里作者给出一个读取属性(properties)文件的单态类,作为单态类的一个实用的例子。 属性文件如同老式的视窗编程时的.ini文件,属于系统的“资源“,而读取属性文件即为资源治理, 显然应当由一个单态类负责。