OO随笔(关于connection pool系列的补充,兼答bonmot)
说起OO, 每个人都有每个人自己的见解。粗浅者如“obj.method的语法就是OO”;高深的则必侃“design pattern”.
今天我也来说说我的一孔之见。
什么是OO?
就是面向接口编程。无论你是用vtable, 或gp的function object, 或就是C的函数指针,正交分解也好,各种pattern也罢,都是面向接口编程思想的一种实现。
为什么要面向接口编程?
为了解耦。
什么是解耦?
就是把程序中互相不相关或有限相关的模块分割开来。就象收拾屋子,你希望把不同的东西放到不同的地方。把酱油和醋倒进不同的瓶子里去。
这里,对完全不相关的功能,可以简单地分开实现。
但事实上,很多情况下,不同模块之间是有互相之间的关系的。这时,就需要接口。用接口准确定义模块之间的关系。解耦前,两个模块之间共享所有信息(这个信息包括数据,也包括各自的实现细节)。解耦后,需要共享的信息被准确地定义在接口中。同时,信息的流向也被确定。
解耦的好处是什么呢?
首先,程序变得清晰了。
其次,不该暴露的实现细节被隐藏了。代码的修改变容易了。
再次,结构灵活了,通过静态多态(function object)或动态多态(vtable), 一个模块可以和任意实现接口的模块协作。原来类A只能与类B协作,解耦后可以和所有实现接口IB的类如B1, B2, ... 协作了。扩展性大大增强。自然而然就代码重用了。
编译依赖也没有了。你可以专心写和编译一个模块,不用等待其它模块的完成。
调试容易了。只要模块对一个接口调试成功,其它的接口也没有问题。于是,甚至可以用一个simple naive的实现该接口的dummy类来调试。(这点,使用template的gp不适用)
那么解耦的坏处是什么呢?
接口的定义变得很关键。解耦就是隐藏一些信息,定义一些需要共享的信息。如果接口定义的不好,隐藏了不该隐藏的信息,那么对某些需要这些信息的复杂情况来说,这个解耦就失败了。
而如果没有隐藏一些应该隐藏的信息,那么不该有的耦合仍然存在。
那么怎样解耦,又怎样定义接口呢?
这是一个纯粹业务逻辑的思考过程。这里,对编程语言的知识变得无关紧要。事实上,只要精确掌握需求,严密地分析需求和模块内部子模块之间的需求,任何一个会逻辑思考的人都可胜任这个工作。就象歌星郑智化一样,虽然不识谱,但一样写歌,只不过最后要懂谱的人把歌纪录下来。
解耦的原则很简单:精确定义需求,仔细分析需求。不要隐藏任何“需求”也许会需要的信息。不要放过任何“需求”明显不需要的信息。
而对需求不清楚的情况,宁可错放一千,不能错杀一个。总而言之,决不能隐藏可能需要的信息。
不考虑重用,重用是解耦后的自然结果。不能倒因为果!
至于对这些原则的具体的运用在前面几篇的connection pool的文章里已经有所体现了。
下面,我先针对bonmot对我的connection pool的例子的疑问进行回答。最后,再对bonmot的一个问题给出我的解决的思路。
无关紧要的问题:
1.ConnectionFactoryImpl也可以继承方式实现ConnectionFactory
其实,我最初的实现,的确是ConnectoinFactoryImpl implements ConnectionFactory的,
但后来,当我overload了instance()函数之后,我发现,这两个函数返回的ConnectionFactory的实现类的代码是不同的。于是,匿名类就诞生了。
这里,有一点值得吹嘘的是,对构造函数的隐藏,使得使用ConnectionFactoryImpl的客户代码对我的改动完全不敏感。这也就是我为什么一直鼓吹要隐藏构造函数的原因。
以下是这个类的实现:
public class ConnectionFactoryImpl
{
private ConnectionFactoryImpl(){}
static public ConnectionFactory instance(final String driver, final String url,
final String user, final String pwd)
throws SQLException, ClassNotFoundException{
final Class driverClass = Class.forName(driver);
return new ConnectionFactory(){
private final Class keeper = driverClass;
public final Connection createConnection()
throws SQLException{
return DriverManager.getConnection(url,user,pwd);
}
};
}
static public ConnectionFactory instance(final String driver, final String url)
throws SQLException, ClassNotFoundException{
final Class driverClass = Class.forName(driver);
return new ConnectionFactory(){
private final Class keeper = driverClass;
public final Connection createConnection()
throws SQLException{
return DriverManager.getConnection(url);
}
};
}
}
2.ConnectionFactoryImpl中
private final Class keeper = driverClass;//似乎多余
是啊,很多代码里都是秃秃的Class.forName(classname)。也工作的很好。不过,记得在哪篇文章里看到过,在新的java language specification里,动态加载的类有可能被垃圾回收。如果是这样,那不麻烦啦?我好容易Class.forName()加载了driver类,好嘛!哪天jvm一高兴给我回收啦!所以咱还是以防万一的好!
功能的问题
1.ConnectionPooling是实现pooling的算法,其最基本的就是getConnection(),releaseConnection(Conn)
为什么不直接在ConnectionPool定义releaseConnection()方法,而要多一个interface ConnectionHome
首先,我的ConnectionPool接口是直接给用户使用的。我在该文的第一章就提出,向用户暴露releaseConnection(Connection)是不好的。你怎样保证用户没有向oracle连接池中返回sql server连接?怎样保证用户不会把同一个连接向连接池返回两次?已经有Connection.close(), 用户为什么要调用releaseConnection?
ConnectionHome接口是PooledConnection类定义的。PooledConnection作为一个封装在物理Connection外的与pool协同工作的类,它需要知道怎样返还一个物理Connection. ConnectionHome接口只定义了一个方法:void releaseConnection(Connection), 就是描述这一需求的产物。
2.事物总是对等的,Factory用于实现物理连接,同样应该负责关闭物理连接,而不应该让pooling算法关闭物理连接。另外,获取与关闭connection应该在一个接口中实现,如果分成2个接口,就不能保证连接的实现一定对应于关闭的实现。
即Factory是物理层,pool是cache层,client是应用层。
首先,ConnectionPooling作为一个描述pooling算法的接口,它需要代表所有可能的pooling算法,所以,我们不能排除在某种pooling算法中,它会以一定的逻辑关闭物理数据库连接。因此,pooling算法一定要可以在任何时候关闭这个连接。
至于是调用Connection.close(), 还是放一个closeConnection方法在ConnectionFactory中,让我们先看看一些其它的factory的实现。
在COM中,IFactory的接口负责生产对象。但释放对象是由IUnknown::Release()负责的。
在Java中,很多Factory接口负责生产对象,但垃圾收集负责回收对象。
为什么这些factory的机制不要求生产者来销毁对象呢?
其原因在于类型安全!
举个例子:
class Factory{
public Object getObject(){
if(...)return new ClassA();
else return new ClassB();
}
public void release(Object obj){
if(obj 是ClassA){
((ClassA)obj).closeA();
}
else{
((ClassB)obj).closeB();
}
/*丑啊!*/
}
}
在这样一个工厂里,getObject方法知道生产的对象的真正类型。但在返回之后,该对象的真正类型就被丢失了。
这样,如果你再把对象送还给工厂,说:“嘿!这是从你们厂出的,现在我不用了,还给你。”对工厂来说,它需要:
1,确认这个对象真是出自本厂。(这可不那么容易)
2,确认这个对象是怎么造出来的。以便找出相应的销毁机制(也不容易)
我们为什么不把releaseConnection对用户公开?也是因为考虑到用户可能会错误返还非本厂生产的东西。
其实,当对象出厂之后,只有对象自己才知道怎样销毁自己。其它任何对象,包括生产者,都无能为力。
4.可靠性不够。表现在:
a.pool的可靠性应该与server的可靠性无关,即database server或socket server可能由于某些原因重新启动,但pool不应该也要重新启动(比如一个pool存有不同server的connection),否则就跑出错误。所以,pool因该检查物理connection的连接状态
怎么说呢?这属于ConnectionPool这个接口的语义。我们是否想让我们的pool即使数据库server崩溃了也能工作呢?
首先,这样做是否有意义呢?如果数据库server崩溃了,我们的Connection pool怎么补救呢?
其次,就算这样是有意义的,它也是ConnectionPooling的逻辑。完全可以交给一个对此负责的ConnectionPooling处理。
b.pooled Connection可能由于一个client忘记关闭,而导致整个pool阻塞。所以,应该对pooled Connection进行监控,对于超时的或其他invaild状态的pooled connection强制回收。
这个问题提的好!起初,我觉得这也只是另一个ConnectionPooling的逻辑。可以交给一个监测已分配的连接使用情况的ConnectionPooling实现来处理。但仔细一想。这样做是不好的。
首先,监视连接的使用一定会需要在连接对象上记录一些状态,象连接分配的时间,最近一次客户使用该连接的时间等等。而ConnectionPooling的语义是返回pool里的物理连接,而由ConnectionPooling2Pool类来做封装。这样,ConnectionPooling的实现就很难纪录必要的信息。当然,ConnectionPooling也可以在返回物理连接前先做一个wrapper, 把信息纪录在这个wrapper里。可是,这样一来,类型安全就得不到保障。在使用该wrapper时,就要进行downcast.
其次,监视已分配连接和管理空闲连接之间到底有多大耦合呢?能否对它们解耦呢?经过分析,我感觉,答案是:不能。监视已分配连接的算法理论上有可能需要知道空闲连接的一些信息,而反之也是一样。而且,更讨厌的是,它们之间所需要的信息量无法估计,也就是说,对一些特定的算法,它们可能是完全的紧耦合。如果按这样分析,这种ConnectionPool可能还得要求实现者直接实现ConnectionPool, 就象我们第三章里使用的方法,只能偶尔使用一些utility类,象PooledConnection之类。
不过,虽然我们不能完全把监视算法和分配算法分开。但事实上很多监视算法,分配算法确实是互不相关的。我们也许可以写一个框架,简化对这些互不相关的算法的实现。虽然对完全紧耦合的情况我们无能为力,但对多数普通的情况,我们还是可以有些作为的。而且,这样一个框架并不影响对复杂的紧耦合情况的特殊实现。
这个框架,当然应该和我们现有的框架协同工作。具体的实现思路,我将在后面给出。
c.ConnectionPoolingImpl
public final synchronized void clear(){
closeAll();
freeConn.removeAllElements();
}//没有transaction保证,有可能引起数据不一致,资源(connection)泄漏(connection没关闭,pool却拿掉了)
可以关闭一个connection,去掉一个pool对象
这里不需要transaction保证的。我们先关闭所有连接,然后再清连接池,怎么可能“connection没关闭,pool却拿掉了”呢?
扩展的问题
1.ConnectionPool是否定义成一个结构interface更好,而让pooling实现pooling算法。
pool可定义成Vector,Tree,...,负责存储遍历,而pooling负责check in,check out.
数据结构和算法永远是紧耦合的。实际上,算法决定数据结构,不可能实现定义一个数据结构,然后强迫所有算法使用。即使是Collection, Iterator之类较抽象的结构也不行。
2.可能有大型的pool,比如字库,因此有检索问题
这就是ConnectionPooling的实现者要动的脑筋了。我们的框架只定义语义和责任分工,并不牵扯这样的实现细节。
2.更复杂的是可能每个connection上有多个引用,pooling要负责给client引用最少的那个connection.
这还是一个实现的细节。不过我想不出有什么理由我们会要不同客户共享同一个连接。这是不安全的,不是吗?
3.可能同一个pool存储不同类型的对象,对不同对象的处理是否可用visitor模式。
还是ConnectionPool的实现者的事。