单例模式属于创建型模式,目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。考虑这样一种对象,这个对象应该在程序启动时被创建,并且在结束时被删除,如应用程序的基础高层对象。通过这个对象可以得到系统中其他的对象,这些基础对象可能是前面提到的工厂对象(Factories),用来创建其他对象;也可能是管理器对象(managers),负责控管其他对象;或者是全局注册表(Registry)。类似这种类型的对象不该被创造出多份。
4.3.1 单例模式的实现
下面给出单例模式的实现。单例模式参与者如图4.8所示。其中Singleton定义了一个getInstance操作,允许客户访问它的惟一实例。可能负责创建它的惟一实例。
代码4.33,可以看到,经典单例模式的实现非常简单。而正是由于概念和实现上的简单,没有顾及到逻辑概念上、测试性、全局依赖、类装载器、序列化、线程安全以及不同JVM之间等等方面的问题,造成了很多的误用。
代码4.33 Singleton.java
package chapter4.pattern.singleton; public class Singleton { private static Singleton instance; /** * 不允许通过构造字来实例化 */ private Singleton() { } /** * 使用synchronized关键字,保障Singleton的线程安全 */ public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
package chapter4.pattern.singleton; import chapter4.pattern.factory.dipioc.ConcreteUtil; import chapter4.pattern.factory.dipioc.UtilService; public class UtilServiceSingletonFactory { private static UtilServiceSingletonFactory instance; /** * 允许子类实例化 */ protected UtilServiceSingletonFactory() { } public synchronized static UtilServiceSingletonFactory getInstance() { if (instance == null) { instance = new UtilServiceSingletonFactory(); } return instance; } public UtilService make() { return new ConcreteUtil(); } }
4.3.2 单例注册表
接下来将要介绍Singleton模式的另一种常用实现,即单例注册表(Singleton Registry),如代码4.35~4.39所示。
代码4.35 FactorySingletonRegistryUsage.java
package chapter4.pattern.singleton; public class FactorySingletonRegistryUsage { public static void main(String[] args) { //实例化工厂注册表 FactorySingletonRegistry registry = FactorySingletonRegistry.getInstance(); //<- 通过反射机制,使得注册表可以依据给定的工厂全限定名返回具体工厂实例 //第一次索取 BeanFactory xmlBeanFactory1 = registry.getBeanFactory("chapter4.pattern.singleton.XmlBeanFactory"); BeanFactory listableBeanFactory1 = registry.getBeanFactory("chapter4.pattern.singleton.ListableBeanFactory"); //第二次索取 BeanFactory xmlBeanFactory2 = registry.getBeanFactory("chapter4.pattern.singleton.XmlBeanFactory"); BeanFactory listableBeanFactory2 = registry.getBeanFactory("chapter4.pattern.singleton.ListableBeanFactory"); //-> //比较先后两次索取的工厂实例,希望得到同一实例的工厂引用,结果正确 System.out.println(xmlBeanFactory1.hashCode() == xmlBeanFactory2.hashCode()); System.out.println(listableBeanFactory1.hashCode() == listableBeanFactory2.hashCode()); } }
import java.util.HashMap; import java.util.Map; public class FactorySingletonRegistry { private static FactorySingletonRegistry instance; private static Map factoryMap = new HashMap();① private FactorySingletonRegistry() { } public synchronized static FactorySingletonRegistry getInstance() { if (instance == null) { instance = new FactorySingletonRegistry(); } return instance; } public synchronized BeanFactory getBeanFactory(String factoryClassName) {② BeanFactory factory = (BeanFactory)factoryMap.get(factoryClassName); if (factory != null) return factory; try { factory = (BeanFactory)Class.forName(factoryClassName).newInstance();③ } catch (ClassNotFoundException e) { System.out.println("Couldn't find class " + factoryClassName); } catch (InstantiationException e) { System.out.println("Couldn't instantiate an object of type " + factoryClassName); } catch (IllegalAccessException e) { System.out.println("Couldn't access class " + factoryClassName); } factoryMap.put(factoryClassName, factory); return factory; } } 代码4.37 BeanFactory.java public interface BeanFactory { }
代码4.38 ListableBeanFactory.java
public class ListableBeanFactory implements BeanFactory {
public ListableBeanFactory() {
System.out.println("ListableBeanFactory Created");
}
}
代码4.39 XmlBeanFactory.java
public class XmlBeanFactory implements BeanFactory {
public XmlBeanFactory() {
System.out.println("XmlBeanFactory Created");
}
}
对单例注册表的简单运作方式,做如下说明:
(1)请看代码4.36,首先这个类是一个单例类,在①处使用了Map对象,这是一个类,持有聚集的讯息。所谓聚集通常就是在Map中以名值对(field-value)的形式存储一系列各类对象的实例引用,通过put(field,value)存储,通过get(field)取得value。在代码4.36的getBeanFactory方法中,通过对factoryMap的存取,可以使该单例注册表持有任意数量的工厂实例,并且通过if (factory != null)的判断,保证了返回的是对同一个工厂实例的引用。
(2)注意代码4.36中的②处,getBeanFactory(String factoryClassName)是一个参数化的工厂方法,和在代码4.26中的类似。
说明:如果工厂方法依赖一个参数标识来决定产品的具体生产行为,那么可以说这就是一个参数化工厂方法。参数化工厂的好处是,创建同一产品族系(拥有同一接口的具体产品)时,不再需要衍生具体的工厂子类来对应。主要缺点是工厂职责过于集中,另外一个缺点仍然是所有传统工厂模式的通病,就是由于硬编码的关系,需要使用if/else语句来决定生产方式,无法摆脱Product = new ConcreteProduct()这种代码带来的强依赖性,同时限制了工厂创建产品的种类数目。这也意味着每当引入新的产品时,就需要重新变更关联代码。
(3)在代码4.36中引入了反射(Reflection)技术,如③所示。在代码4.35中,可以以类的全限定名作为参数传入工厂方法,工厂方法会通过反射技术来实例化那个类,而不需要预先在工厂方法中记载有限的产品集合了。
至此,给出了单例模式的基本用法,单例模式是一种常用的模式,但也很容易被误用。最后还提到了反射和工厂方法模式的一些结合,这是非常有用的技术。