当前位置: 首页 > 工具软件 > 豆豆Pool > 使用案例 >

OO随笔(关于connection pool系列的补充,兼答bonmot)

公良子轩
2023-12-01

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的实现者的事。

 

 类似资料: