对象数量 Object quantity
这里描述的两种模式都可以单独用来控制对象的数量。
单件(Singleton)实际上可以认为是对象池(Object Pool)的一个特例,但是对象池(Object Pool)的应用和单件(Singleton)是如此的不同,将二者区分开来对待是非常必要的。
单件(Singleton)
单件(Singleton)提供一种方法使得某一特定类型存在一个,并且只能是一个对象。它可能是最简单的模式了。单件(Singleton)应用的一个重要方面是提供一个全局的存取点。单件(Singleton)是C里面全局变量的一个替代方法。
除此之外,单件还常常提供注册(registry)和查找(lookup)的功能――你可以从单件那里找到其它对象的引用(find references to other objects)。
单件(Singleton)可以在java库里找到,但是下面提供了一个更直接的例子。
//: singleton:SingletonPattern.java
// The Singleton design pattern: you can
// never instantiate more than one.
package singleton;
import junit.framework.*;
// Since this isn't inherited from a Cloneable
// base class and cloneability isn't added,
// making it final prevents cloneability from
// being added through inheritance:
final class Singleton {
private static Singleton s = new Singleton(47);
private int i;
private Singleton(int x) { i = x; }
public static Singleton getReference() {
return s;
}
public int getValue() { return i; }
public void setValue(int x) { i = x; }
}
public class SingletonPattern extends TestCase {
public void test() {
Singleton s = Singleton.getReference();
String result = "" + s.getValue();
System.out.println(result);
assertEquals(result, "47");
Singleton s2 = Singleton.getReference();
s2.setValue(9);
result = "" + s.getValue();
System.out.println(result);
assertEquals(result, "9");
try {
// Can't do this: compile-time error.
// Singleton s3 = (Singleton)s2.clone();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
junit.textui.TestRunner.run(SingletonPattern.class);
}
} ///:~
单件模式的关键是防止用户以其它任何方式创建对象,而只能用你所提供的方式。所有的构造函数必须被声明为私有的(private),而且必须至少声明一个构造函数,否则编译器就会以package权限帮你创建一个默认的构造函数。
在这一点上(at this point),你自己决定如何创建对象。上面的例子里,对象是静态(statically)创建的,但是你也可以等到客户端提出请求需要创建对象时才创建。不管是哪种情况,(创建的)对象必须以私有方式(privately)存储,而通过公有方法(public methods)来访问。上面的例子里,getReference()返回对单件对象的引用。剩下的接口(getValue()和setValue())是常规的类接口。
另外,Java允许使用克隆(cloning)来创建对象。这个例子里,把这个类声明为final就是为了防止通过克隆方法创建对象。因为Singleton是直接从Object继承下来的,由于clone()是受保护的(protected)方法因而不能用来(复制对象),如果这么做就会导致编译时错误。
但是,如果以public的方式继承了一个重载了clone()方法的类,并且实现了Cloneable接口的话,为了防止对象被克隆,就必须重载clone()方法并抛出一个CloneNotSupportedException异常。这一点在Thinking in Java 第二版的附录A里也讲到了。(你也可以重载clone()方法使它只返回this,但是这样做有一定的欺骗性,客户端程序员会想当然的认为他是在克隆(复制)那个对象,但实际上他处理的还是原来那个对象。)实际上,这么说并不确切,即使是上面所说的情况,你仍然可以使用反射(reflection)来调用clone()方法(真的是这样么?因为Clone()方法是受保护的(protected),所以我也不那么肯定。如果真是这样的话,那就必须得抛出CloneNotSupportedException 异常,这是保证对象不被克隆的唯一方法了。)
练习
1. SingletonPattern.java 总是创建一个对象,即使这个对象从来不会被用到。请用延迟初始化(Lazy Initialization)的方法修改这个程序,使单件对象只在它第一次被用到的时候才创建。
2. Create a registry/lookup service that accepts a Java interface and produces a reference to an object that implements that interface.
对象池(Object pool)
并没有限制说只能创建一个对象。这种技术同样适用于创建固定数量的对象,但是,这种情况下,你就得面对如何共享对象池里的对象这种问题。如果共享对象很成问题得话,你可以考虑以签入(check-in)签出(check-out)共享对象作为一种解决方案。比如,就数据库来说,商业数据库通常会限制某一时刻可以使用的连接的个数。下面这个例子就用对象池(object pool)实现了对这些数据库连接的管理。首先,对象池对象(a pool of objects)的基本管理是作为一个单独的类来实现的。
//: singleton:PoolManager.java
package singleton;
import java.util.*;
public class PoolManager {
private static class PoolItem {
boolean inUse = false;
Object item;
PoolItem(Object item) { this.item = item; }
}
private ArrayList items = new ArrayList();
public void add(Object item) {
items.add(new PoolItem(item));
}
static class EmptyPoolException extends Exception {}
public Object get() throws EmptyPoolException {
for(int i = 0; i < items.size(); i++) {
PoolItem pitem = (PoolItem)items.get(i);
if(pitem.inUse == false) {
pitem.inUse = true;
return pitem.item;
}
}
// Fail early:
throw new EmptyPoolException();
// return null; // Delayed failure
}
public void release(Object item) {
for(int i = 0; i < items.size(); i++) {
PoolItem pitem = (PoolItem)items.get(i);
if(item == pitem.item) {
pitem.inUse = false;
return;
}
}
throw new RuntimeException(item + " not found");
}
} ///:~
//: singleton:ConnectionPoolDemo.java
package singleton;
import junit.framework.*;
interface Connection {
Object get();
void set(Object x);
}
class ConnectionImplementation implements Connection {
public Object get() { return null; }
public void set(Object s) {}
}
class ConnectionPool { // A singleton
private static PoolManager pool = new PoolManager();
public static void addConnections(int number) {
for(int i = 0; i < number; i++)
pool.add(new ConnectionImplementation());
}
public static Connection getConnection()
throws PoolManager.EmptyPoolException {
return (Connection)pool.get();
}
public static void releaseConnection(Connection c) {
pool.release(c);
}
}
public class ConnectionPoolDemo extends TestCase {
static {
ConnectionPool.addConnections(5);
}
public void test() {
Connection c = null;
try {
c = ConnectionPool.getConnection();
} catch (PoolManager.EmptyPoolException e) {
throw new RuntimeException(e);
}
c.set(new Object());
c.get();
ConnectionPool.releaseConnection(c);
}
public void test2() {
Connection c = null;
try {
c = ConnectionPool.getConnection();
} catch (PoolManager.EmptyPoolException e) {
throw new RuntimeException(e);
}
c.set(new Object());
c.get();
ConnectionPool.releaseConnection(c);
}
public static void main(String args[]) {
junit.textui.TestRunner.run(ConnectionPoolDemo.class);
}
} ///:~
Exercises练习先不翻了
1. Add unit tests to ConnectionPoolDemo.java to demonstrate the problem that the client
may release the connection but still continue to use it.