Java模式设计之单例模式(三)
副标题#e#
一个实用的例子:属性打点器
什么是属性文件
这里给出一个读取属性(properties) 文件的单例类,作为单例模式的一个实用的例子。属性文件如同老式的视窗编程时的.ini 文件,用于存放系统的设置信息。设置信息在属性文件中以属性的方法存放,一个属性就是两个字符串构成的对子,个中一个字符串是键(key),另一个字符串是这个键的值(value)。
大大都的系统都有一些设置常量,这些常量假如是存储在措施内部的,那么每一次修改这些常量都需要从头编译措施。将这些常量放在设置文件中,系统通过会见这个设置文件取得设置常量,就可以通过修改设置文件而无需修改措施而到达变动系统设置的目标。系统也可以在设置文件中存储一些事情情况信息,这样在系统重启时,这些事情信息可以延续到下一个运行周期中。
假定需要读取的属性文件就在当前目次中,且文件名为singleton.properties 。这个文件中有如下的一些属性项。
代码清单5:属性文件内容
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) ,将当前的属性列写入一个输出流,假如这个输出流是导向一个文件的,那么这个要领就将属性流存入文件。
#p#副标题#e#
为什么需要利用单例模式
属性是系统的一种"资源",该当制止有多余一个的工具读取出格是存储属性。另外,属性的读取大概会在许多处所产生,建设属性工具的处所该当在那边不是很清楚。换言之,属性打点器该当本身建设本身的实例,而且本身向系统全程提供这一事例。因此,属性文件打点器该当是一个单例模式认真。
系统设计
系统的焦点是一个属性打点器,也就是一个叫做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;
}
}
}
在上面直接利用了一个局域的常量储存储属性文件的路径。在实际的系统中,读者可以采纳更机动的方法将属性文件的路径传入。
#p#分页标题#e#
读者可以看到,这个打点器类有一个很有意思的成果,即在每一次挪用时,查抄属性文件是否已经被更新过。假如确实已经被更新过的话,打点器会自动从头加载属性文件,从而担保打点器的内容与属性文件的内容老是一致的。
奈何挪用属性打点器
下面的源代码演示了奈何挪用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);
上面代码运行时的环境如下图所示。
感乐趣的读者可以参考阅读本书的"专题:XMLProperties 与适配器模式"一章,哪里对利用Java 属性类和XML 文件名目做了有用的接头。
Java 语言中的单例模式
Java 语言中就有许多的单例模式的应用实例,这里接头较量有名的几个。
Java 的Runtime 工具
在Java 语言内部,java.lang.Runtime 工具就是一个利用单例模式的例子。在每一个Java 应用措施内里,都有惟一的一个Runtime 工具。通过这个Runtime 工具,应用措施可以与其运行情况产生彼此浸染。
Runtime 类提供一个静态工场要领getRuntime()::
public static Runtime getRuntime();
通过挪用此要领,可以得到Runtime 类惟一的一个实例:
Runtime rt = Runtime getRuntime();
Runtime 工具凡是的用途包罗:执行外部呼吁;返回现有内存即全部内存;运行垃圾收集器;加载动态库等。下面的例子演示了奈何利用Runtime 工具运行一个外部措施。
代码清单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 文件,而又不想指明Word 软件安装的位置时,可以利用下面的做法:
Process proc = Runtime.getRuntime().exec(
"cmd /E:ON /c start MyDocument.doc");
在上面,被执行的呼吁是start MyDocument.doc ,开关E:ON 指定DOS 呼吁处理惩罚器答允呼吁扩展,而开关/C 指明后头跟从的字符串是呼吁,并在执行呼吁后封锁DOS 窗口,start 呼吁会开启一个单独的窗口执行所提供的呼吁。
Introspector 类
#p#分页标题#e#
一般的应用措施大概永远也不会直接用到Introspector 类,但读者应该知道Introspector 是做什么的。Sun 提供了一个叫做BeanBox 的系统,答允动态地加载JavaBean ,并动态地修改其性质。BeanBox 在运行时的环境如下图所示。
在上面的图中显示了BeanBox 最重要的两个视窗,一个叫做BeanBox 视窗,另一个叫做性质视窗。在上面的BeanBox 视窗中显示了一个Juggler Bean 被安排到视窗中的环境。相应的,在性质视窗中显示了Juggler Bean 的所有性质。所有的Java 集成情况都提供这种成果,这样的系统就叫做BeanBox 系统。
BeanBox 系统利用一种自省(Introspection )进程来确定一个Bean 所输出的性质、事件和要领。这个自省机制是通过自省者类,也即java.util.Introspector 类实现的;这个机制是成立在Java 反射(Reflection) 机制和定名类型的基本之上的。好比,Introspector 类可以确定Juggler Bean 所支持的所有的性质,这是因为Introspector 类可以获得所有的要领,然后将个中的取值和赋值要领以及它们的特征加以较量,从而得出功效。显然,在整个BeanBox 系统中只需要一个Introspector 工具,下面所示就是这个类的布局图。
可以看出,Introspector 类的结构子是私有的,一个静态工场要领instantiate() 提供了Instrospector 类的惟一实例。换言之,这个类是单例模式的应用。
java.awt.Toolkit 类
Toolkit 类是一个很是有趣的单例模式的例子。Toolkit 利用单例模式建设所谓的Toolkit 的默认工具,而且确保这个默认实例在整个系统中是惟一的。Toolkit 类提供了一个静态的要领getDefaultToolkit() 来提供这个惟一的实例,这个要领相当于懒汉式的单例要领,因此整个要领都是同步化的。
代码清单9:getDefaultToolkit() 要领
public static synchronized Toolkit
getDefaultToolkit()
{
......
}
Toolkit 类的类图如下所示。
个中性质defaultToolkit 实际上就是静态的getDefaultToolkit 类。有趣的是,由于Toolkit 是一个抽象类,因此其子类假如提供一个私有的结构子,那么其子类即是一个正常的单例类;而假如其子类作为详细实现提供一个果真的结构子,这时候这个详细子类即是" 不完全"的单例类。关于"不完全"的单例类的接头请见本章后头的"专题:不完全的单例类"一节。
模版要领模式
同时,熟悉模版要领模式的读者可以看出,getDefaultToolkit() 要领实际上是一个模版要领。私有结构子是推迟到子类实现的剩余逻辑,按照子类对这个剩余逻辑的差异实现,子类就可以提供完全差异的行为。对Toolkit 的子类而言,私有结构子依赖于操纵系统,差异的子类可以按照差异的操纵系统而给出差异的逻辑,从而使Toolkit 的子类对差异的操纵系统给出差异的行为。
javax.swing.TimerQueue 类
这是一个不完全的单例类,由于这个类是在Swing 的按时器类中利用的,因此我们将在后头先容。