从一个ConnectionPool的实现看design pattern的运用 (六)
要对不同资源重用pooling的算法?先让我们再从头审视一下我们ConnectionPool的实现。
1。 Pooling算法由ConnectionMan来实现。它需要委托ConnectionFactory来具体创建Connection对象
2。 ConnectionFactory负责建立连接。它封装了如何建立连接
3。 PooledConnection负责封装Connection对象,修改close()等跟pooling有关的方法,并对其它方法进行委托。
4。 ConnectionMan2ConnectionPool负责使用PooledConnection来把一个不能对用户容错,对用户不透明的ConnectionMan转化成对用户安全透明的ConnectionPool.
首先,PooledConnection是无法重用的。它仅仅实现了Connection接口,重写了close()方法。而对于一个未知的资源,我们自然是无法事先写一个实现,无法未卜先知地知道哪一个方法是负责释放的。
其次,ConnectionMan2ConnectionPool, 因为它直接使用了PooledConnection, 也无法对不同的资源pool重用。换句话说,对不同的资源,我们必须对应地写封装类和转换类。
ConnectionFactory是一个接口,我们是可以写一个更generic的ResourceFactory, 来让不同的ResourcePool重用它。
重用Pooling算法是关键。而能否重用依赖于我们把所有与具体Resource相关的细节抽取出来。在此算法中,什么是与具体资源相关呢?资源创建自然是一个,我们已经用一个abstract factory把它抽取出来了。另一个跟具体资源相关的是资源的释放。我们用close()方法释放Connection. 但对于其它的资源,也许那是一个release()方法,destroy()方法,甚至是没有任何方法。
所以,为了重用pooling算法,我们还需要抽象资源释放。一个ResourceCollector的接口应该能够完成这样的工作。(如果说,ConnectionFactory是abstract factory pattern, 为什么GOF的书里没有一个针对于ResourceCollector的pattern呢?)
好了,在写代码之前,还有一点需要澄清:
许多C++程序员认为,C++的GP是一个比OO更有用的技术。其实,GP中最主要的function object的思想,和OO中的面向接口编程只是一对孪生兄弟罢了。Template比目前Java的OO的主要优点在于:1,效率高。2,减少了很多类型强制转换(downcast).
而许多Java程序员则认为, Java不需要GP, 因为所有GP能做的事,OO都能做。如GP可以有vector<string>, Java则有Vector, 只要做些downcast就可以了。但他们忘记了,静态类型检查是Java这种强类型语言的原则,也是所有程序员应该尽力遵循的。靠downcast的程序是容易出错的。所以,Generic Java (加入generics的Java) 已经是下一版Java的目标了。
说这些题外话的目的是,使用目前的Java语法,即使我们可以写出可以重用的ResourcePool的框架来,它也是以牺牲程序的类型安全来保障的。用户需要显式地加入downcast来使用这个框架。
因此,既然我们反正也只是学术上的探讨,让我们在这里使用类似Generic Java的语法,来使我们的程序看起来更美。
首先是ResourceFactory接口:
public interface ResourceFactory<R>{
R createResource();
}
ResourceCollector接口:
public interface ResourceCollector<R>{
void closeResource(R r);
}
ResourceMan接口,仍然是一个对实现者友好,但需要封装才能交给客户的接口。
public interface ResourceMan<R>{
R getResource()
throws Throwable;
void clear();
void releaseResource(R r);
}
下面是一个使用我们的ConnectionManImpl的pooling逻辑的ResourceMan的实现。
public class ResourceManImpl<R> implements ResourceMan<R>{
typedef ResourceMan<R> Man;
typedef ResourceFactory<R> Factory;
typedef ResourceCollector<R> Collector;
private final Factory factory;
private final Collector collector;
private int client;
private final Vector<R> freeList = new Vector<R>();
private final int maxClients;
static public Man instance(Factory f, Collector c, int m){
return new ResourceManImpl(f, c, m);
}
private ResourceManImpl(Factory f, Collector c, int m)
{
this.maxClients = m;
this.factory = f;
this.collector = c;
}
public final synchronized void releaseResource(R resource)
{
freeList.addElement(resource);
client--;
notify();
}
public final synchronized R getResource()
throws Throwable
{
R ret;
如果pool里有R
从pool中去掉一个R r;
clients++;
ret = r;
否则,如果clients<maxClients
r = factory.createResource();
clients++;
ret = r;
否则,wait(),直到pool中有空闲R
return ret;
}
public synchronized void clear(){
对每一个再free list中的资源r:
collector. closeResource (r);
freeList.removeAllElements();
}
}
呵呵,这样,你的公司可以雇佣几个只钻研各种pooling算法的人,让他们什么都不想,只写不同的pooling的算法,也就是不同的ResourceMan的实现。
对于一个具体的资源,我们只需要给出ResourceFactory和ResourceCollector的实现,就可以实例化一个ResourceMan, 再根据具体的资源,写出封装类和转换类,(这其中,封装类稍微麻烦一些,但也不过是一些委托和几个跟pooling相关的特殊函数的处理),一个对那个具体资源的pool就可以出炉了。你甚至可以成立一个公司,专门卖各种pool给其他的开发公司。
于是,假设你手头有五种pooling算法,三种需要pool的资源,每种资源有四种创建方法,两种释放方法,那么,你就可以任意组合,而形成5*3*4*2=90种不同的pool. 这就是面向接口编程的威力!你不再是仅开发一个顺序执行的流程,不再是在已有的一些类上面做一些修修改改,扩展覆盖。你是把一个一个插座和插头插到一起去!只要两者的接口一致,你就可以任意组合。
美妙吧?更美妙的是,不知道你注意到没有,在我们的实现中,并没有对某种特定情况的假设。你不会因为我们对某种你用不到的功能的照顾而牺牲到你想用的功能的效率。你不会拿到一个1000行的代码,而其中900行都是为实现你根本用不到的功能。
当然,要说效率没有因为照顾一般性而受到丝毫影响,却也不是。请注意ResourceManImpl.clear()方法。这个方法的目的是释放所有在池中的空闲资源,并清空资源池。
我们目前的实现是,遍历池中所有资源,调用ResourceCollector.closeResource方法来释放。然后再清空资源池。
这对于Connection Pool是很好的。我们只需要实现一个简单的ConnectionCollector, 就象这样:
public class ConnectionCollector implements ResourceCollector<Connection>{
public void closeResource(Connection conn){
conn.close();
}
private ConnectionCollector(){}
private static final test.res.ResourceCollector<Connection> singleton = new ConnectionCollector();
public static test.res.ResourceCollector<Connection> instance(){return singleton;}
}
然后把它交给一个我们选定的ResourceMan的实现类。
但是,实际上,并不是所有的资源都需要显式释放的。如thread, 我们只需要去掉对该thread的引用,它就会最终被垃圾收集器释放。我们根本不需要遍历空闲线程来释放它们。
当然,你可以传给这个ResourceMan一个ResourceCollector, 在它的closeResource方法里,什么也不干。这样,功能还是被实现了。
不过,这种方法的不足之处在于,它把一个本来可以是O(1)的clear()方法变成了O(n)的。Java虽然并不对效率锱殊必较,比如,你不需要对一个虚函数的调用开销过于担心。但增大一个方法的复杂度总是一个非常非常不好的事。
而且,(仅仅是想象)也许对一些特殊的资源,它的回收一定要基于某种逻辑次序呢?
如何解决这种对所有空闲资源的释放的策略问题呢?如果要求写pooling算法的人,对不需要调用资源释放,或者有特殊释放顺序要求的资源再重新写一个pooling的实现,显然是不好的。他怎么重用他那些聪明的pooling算法呢?copy paste吗?
相信许多人的第一感一定还是:重载clear().
但,就象我们前面说的:
第一:这会产生过多的类
第二:占用了唯一的父类的位置。Java里一个类只能有一个父类,如果你的框架要求用户继承你的类,你也就剥夺了用户继承其它类的权利。
第三:父类子类之间的耦合是大型系统的大敌。父类一旦发布出去,再想更改(比如说,加个私有方法什么的),非常困难!(因为你不知道是否某个子类已经使用了这个函数signature.)
怎么办呢?还是interface啊!
我们可以加入一个负责回收所有资源的接口。然后实现一个什么也不做得类给thread pool. (保证了clear()的O(1)的运算复杂度);再实现特殊顺序回收给我们假想的资源;再实现普通的顺序回收类给一般的资源如connection.
这个接口类似于这样:
public interface ResourcesCollector<R>{
public void collect(Collection<R> rs);
}
修改后的ResourceManImpl的代码是这样:
public class ResourceManImpl<R> implements ResourceMan<R>{
typedef ResourceMan<R> Man;
typedef ResourceFactory<R> Factory;
typedef ResourcesCollector<R> Collector;
private final Factory factory;
private final Collector collector;
private int client;
private final Vector<R> freeList = new Vector<R>();
private final int maxClients;
static public Man instance(Factory f, Collector c, int m){
return new ResourceManImpl(f, c, m);
}
private ResourceManImpl(Factory f, Collector c, int m)
{
this.maxClients = m;
this.factory = f;
this.collector = c;
}
public final synchronized void releaseResource(R resource)
{
freeList.addElement(resource);
client--;
notify();
}
public final synchronized R getResource()
throws Throwable
{
R ret;
如果pool里有R
从pool中去掉一个R r;
clients++;
ret = r;
否则,如果clients<maxClients
r = factory.createResource();
clients++;
ret = r;
否则,wait(),直到pool中有空闲R
return ret;
}
public synchronized void clear(){
collector. collect(freeList);
freeList.removeAllElements();
}
}
给thread pool的什么也不干的ResourcesCollector的实现是这样:
public class NopResourcesCollector implements ResourcesCollector<R>{
private static final NopResourcesCollector singleton = new NopResourcesCollector();
private NopResourcesCollector(){}
public void collect(Collection<R> rs){}
public static ResourcesCollector<R> instance(){
return singleton;
}
}
(题外话:这里的NopResourcesCollector有点特别,它不是一个带类型参数的类,相反,它是一个普通的类,但它却实现了任意ResourcesCollector<R>接口。
这种特殊的语法是C++ template和Generic Java所不支持的。它是实验室里的一种“趣向”。它的作用是避免了多个singleton实例。对所有的资源R, 无论是Connection, Thread 还是其它什么,都共享一个NopResourcesCollector. 这是安全的,因为不管怎么说, NopResourcesCollector并不做任何事情。)
给Connection pool之类的普通得ResourcesCollector的实现是这样:
public class LinearResourcesCollector<R> implements ResourcesCollector<R>{
private final ResourceCollector<R> c;
private LinearResourcesCollector(ResourceCollector<R> c){
this.c = c;
}
public void collect(Collection<R> rs){
对Collection中的每个r
c.collect(r);
}
public static ResourcesCollector<R> instance(ResourceCollector c){
return new LinearResourcesCollector<R>(c);
}
}