Java模式设计之单例模式(三)
这里给出一个读取属性(properties) 文件的单例类,作为单例模式的一个实用的例子。属性文件如同老式的视窗
假定需要读取的属性文件就在当前目录中,且文件名为singleton.properties 。这个文件中有如下的一些属性项。
| node1.item1=How node1.item2=are node2.item1=you node2.item2=doing node3.item1=? |
例如,node1.item1 就是一个键,而How 就是这个键所对应的值。
Java 属性类
Java 提供了一个工具类,称做属性类,可以用来完成Java 属性和属性文件的操作。这个属性类的继承关系可以从下面的类图中看清楚。

属性类提供了读取属性和设置属性的各种方法。其中读取属性的方法有:
.. contains(Object value) 、containsKey(Object key): 如果给定的参数或属性关键字在属性表中有定义,该方法返回True ,否则返回False。
.. getProperty(String key)、getProperty(String key, String default) :根据给定的属性关键字获取关键字值。
.. list(PrintStream s) 、list(PrintWriter w) :在输出流中输出属性表内容。
.. size():返回当前属性表中定义的属性关键字个数。
设置属性的方法有:
.. put(Object key, Object value) :向属性表中追加属性关键字和关键字的值。
.. remove(Object key):从属性表中删除关键字。
从属性文件加载属性的方法为load(InputStream inStream),可以从一个输入流中读入一个属性列,如果这个流是来自一个文件的话,这个方法就从文件中读入属性。
将属性存入属性文件的方法有几个,重要的一个是store(OutputStream out, String header) ,将当前的属性列写入一个输出流,如果这个输出流是导向一个文件的,那么这个方法就将属性流存入文件。
为什么需要使用单例
属性是系统的一种"资源",应当避免有多余一个的对象读取特别是存储属性。此外,属性的读取可能会在很多地方发生,创建属性对象的地方应当在哪里不是很清楚。换言之,属性管理器应当自己创建自己的实例,并且自己向系统全程提供这一事例。因此,属性文件管理器应当是一个单例模式负责。
系统的核心是一个属性管理器,也就是一个叫做ConfigManager 的类,这个类应当是一个单例类。因此,这个类应当有一个静态工厂方法,不妨叫做getInstance(), 用于提供自己的实例。
为简单起见,本文在这里采取"饿汉"方式实现ConfigManager 。例子的类图如下所示。

例子的源代码如下所示。
代码清单6:ConfigManager 的源代码
| import java.util.Properties; import java.io.FileInputStream; import java.io.File; public class ConfigManager { /** * 属性文件全名 */ private static final String PFILE = System.getProperty("user.dir") + File.Separator + "Singleton.properties"; /** * 对应于属性文件的文件对象变量 */ private File m_file = null; /** * 属性文件的最后修改日期 */ private long m_lastModifiedTime = 0; /** * 属性文件所对应的属性对象变量 */ private Properties m_props = null; /** * 本类可能存在的惟一的一个实例 */ private static ConfigManager m_instance = 234 Java 与模式 new ConfigManager(); /** * 私有的构造子,用以保证外界无法直接实例化 */ private ConfigManager() { m_file = new File(PFILE); m_lastModifiedTime = m_file.lastModified(); if(m_lastModifiedTime == 0) { System.err.println(PFILE + " file does not exist!"); } m_props = new Properties(); try { m_props.load(new FileInputStream(PFILE)); } catch(Exception e) { e.printStackTrace(); } } /** * 静态工厂方法 * @return 返还ConfigManager 类的单一实例 */ synchronized public static ConfigManager getInstance() { return m_instance; } /** * 读取一特定的属性项 * * @param name 属性项的项名 * @param defaultVal 属性项的默认值 * @return 属性项的值(如此项存在), 默认值(如此项不存在) */ final public Object getConfigItem( String name, Object defaultVal) { long newTime = m_file.lastModified(); // (多数情况是程序员手动)修改过 // 如果是,重新读取此文件 if(newTime == 0) { // 属性文件不存在 if(m_lastModifiedTime == 0) { System.err.println(PFILE + " file does not exist!"); } else { System.err.println(PFILE + " file was deleted!!"); } return defaultVal; } else if(newTime >m_lastModifiedTime) { // Get rid of the old properties m_props.clear(); try { m_props.load(new FileInputStream(PFILE)); } catch(Exception e) { e.printStackTrace(); } } m_lastModifiedTime = newTime; Object val = m_props.getProperty(name); if( val == null ) { return defaultVal; } else { return val; } } } |
读者可以看到,这个管理器类有一个很有意思的功能,即在每一次调用时,检查属性文件是否已经被更新过。如果确实已经被更新过的话,管理器会自动重新加载属性文件, 从而保证管理器的内容与属性文件的内容总是一致的。
怎样调用属性管理器
下面的源代码演示了怎样调用ConfigManager 来读取属性文件。
代码清单7:怎样调用ConfigManager 类以读取属性文件
| BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Type quit to quit"); do { System.out.print("Property item to read: "); String line = reader.readLine(); if(line.equals("quit")) { break; } System.out.println(ConfigManager.getInstance() .getConfigItem(line, "Not found.")); } while(true); |
上面代码运行时的情况如下图所示。

Java
Java 语言中就有很多的单例模式的应用实例,这里讨论比较有名的几个。
Java 的Runtime 对象
Runtime 类提供一个静态工厂方法getRuntime()::
| public static Runtime getRuntime(); |
通过调用此方法,可以获得Runtime 类惟一的一个实例:
| Runtime rt = Runtime getRuntime(); |
代码清单8:怎样使用Runtime 对象运行一个外部命令
| import java.io.*; public class CmdTest { public static void main(String[] args) throws IOException { Process proc = Runtime.getRuntime().exec("notepad.exe"); } } |
上面的程序在运行时会打开notepad 程序。应当指出的是,在Windows 2000 的环境中,如果需要打开一个Word
| Process proc = Runtime.getRuntime().exec( "cmd /E:ON /c start MyDocument.doc"); |
在上面,被执行的命令是start MyDocument.doc ,开关E:ON 指定DOS 命令处理器允许命令扩展,而开关/C 指明后面跟随的字符串是命令,并在执行命令后关闭DOS 窗口,start 命令会开启一个单独的窗口执行所提供的命令。
Introspector 类

在上面的图中显示了BeanBox 最重要的两个视窗,一个叫做BeanBox 视窗,另一个叫做性质视窗。在上面的BeanBox 视窗中显示了一个Juggler Bean 被放置到视窗中的情况。相应的,在性质视窗中显示了Juggler Bean 的所有性质。所有的Java 集成环境都提供这种功能,这样的系统就叫做BeanBox 系统。
BeanBox 系统使用一种自省(Introspection )过程来确定一个Bean 所输出的性质、事件和方法。这个自省机制是通过自省者类,也即java.util.Introspector 类实现的;这个机制是建立在Java 反射(Reflection) 机制和命名规范的基础之上的。比如,Introspector 类可以确定Juggler Bean 所支持的所有的性质,这是因为Introspector 类可以得到所有的方法,然后将其中的取值和赋值方法以及它们的特征加以比较,从而得出结果。显然,在整个BeanBox 系统中只需要一个Introspector 对象,下面所示就是这个类的结构图。

java.awt.Toolkit 类
Toolkit 类是一个非常有趣的单例模式的例子。Toolkit 使用单例模式创建所谓的Toolkit 的默认对象,并且确保这个默认实例在整个系统中是惟一的。Toolkit 类提供了一个静态的方法getDefaultToolkit() 来提供这个惟一的实例,这个方法相当于懒汉式的单例方法,因此整个方法都是同步化的。
代码清单9:getDefaultToolkit() 方法
| public static synchronized Toolkit getDefaultToolkit() { ...... } |
Toolkit 类的类图如下所示。

其中性质defaultToolkit 实际上就是静态的getDefaultToolkit 类。有趣的是,由于Toolkit 是一个抽象类,因此其子类如果提供一个私有的构造子,那么其子类便是一个正常的单例类;而如果其子类作为具体实现提供一个公开的构造子, 这时候这个具体子类便是" 不完全"的单例类。关于"不完全"的单例类的讨论请见本章后面的"专题:不完全的单例类"一节。
模版方法模式
javax.swing.TimerQueue 类
这是一个不完全的单例类,由于这个类是在Swing 的定时器类中使用的,因此我们将在后面介绍。