从一个ConnectionPool的实现看Design Pattern的运用 (二)作者:ajoo好啦,现在让我们看看我们是怎样设计这个ConnectionPool的接口的。
public interface ConnectionPool
{
public Connection getConnection();
public void clear();
}
当然,这只是一个简化的接口。真正实现时也许我们还会有一些其他的方法。如同时支持同步getConnection()和异步getConnection()。同时,这个返回的Connection必须改写close()方法。原来的close()方法是关闭物理连接,但现在,我们要把这个Connection返还到ConnectionPool。因此,这个Connection对象必须知道它出身的ConnectionPool。这样,用户仍然调用Connection.close(), 也不用担心把Connection返还到错误的ConnectionPool。
再来看看我们的实现:
public class ConnectionPoolImpl: implements ConnectionPool
{
class PooledConnection implements Connection
{
private final Connection conn;
private boolean closed;public PooledConnection(Connection conn) throws SQLException
{
this.conn = conn;
closed = conn.isClosed();
}public void close()
{
if(!closed)
{
//保证重复调用close()不会把一个connection重复返还。
closeConnection(conn);
closed = true;
}
}public boolean isClosed()
{
return closed;
}public Statement createStatement() throws SQLConnection
{
if(isClosed())
{
throw new SQLException(“Connection closed”);
}return conn.createStatement();
}public void commit() throws SQLConnection
{
if(isClosed())
{
throw new SQLException(“Connection closed”);
}
conn.commit();
}//其他所有的方法都这样做委托。
}public synchronized Connection getConnection()
{
如果pool里有Connection
从pool中去掉一个Connection conn;
clients++;
return new PooledConnection(conn); 
否则,如果clients<maxClients
生成一个新的连接conn
clients++;
return new PooledConnection(conn);
否则,wait(),直到pool中有空闲Connection 
}//其他的实现都和我们第一章里的代码一样。
}好了,这样,通过内部类PooledConnection这样一个wrapper, 我们就可以实现这个能处理ConnectionPool的Connection。
对了,忘了说了,这个PooledConnection其实就是Design Pattern里的Decorator模式。
现在让我们再欣赏一下我们的代码吧。ConnectonPoolImpl提供了一种基于简单策略的ConnectionPool的实现。PooledConnection封装了数据库连接,使之能和ConnectionPool协同工作。
完美吧?
不过,慢着!PooledConnection只是和ConnectionPoolImpl协同工作。张三要写ConnectionPool2还得自己重新实现一个Decorator。
怎么样能使不同的ConnectionPool的实现重用我们PooledConnection呢?而且,在所有大约二十个委托函数里,我们都有if(isClosed())……, 是不是很眼熟啊?一个Connection一旦被close()之后,只有在涅磐之后(通过ConnectionPool.getConnection()再次返回),才能使用。而所有对关闭了的Connection的调用,返回结果都是一样的.(不行,不行,不行!)
猜到点什么了吗?对啦,State Patten!

解决方案 »

  1.   

    从一个ConnectionPool的实现看Design Pattern的运用 (三)作者;ajoo根据上回对PooledConnection的分析,下面是对一个可重用PooledConnection的实现:public class PooledConnection implements Connection
    {
    public interface Pool
    {
    //引入这个interface, 是因为我们的PooledConnection只需要知道如何返还Connection。
    //本着接口最小化原则,我们只定义我们需要的操作。
    void closeConnection(Connection conn);
    }private interface ConnectionState
    {
    //state pattern的interface.
    public ConnectionState close() throws SQLException;
    //close()方法是唯一引起状态转移的方法。
    boolean isClosed();
    Connection getOpenConnection() throws SQLException;
    }private static class ClosedConnection implements ConnectionState
    {
    public final ConnectionState close()
    {
    return this;
    }
    //当一个Connection已经closed了的时候,它实际上已经死了。所有对它的操作,除了isClosed()和close(), 只产生异常。所以,一个closed的Connection, 它已经不需要保存那个物理数据库连接和对出身ConnectionPool的连接。而且因为所有的 closed connection的状态都一样,所以可以用singleton来节省内存。public final Connection getOpenConnection() throws SQLException
    {
    throw new SQLException("Connection closed");
    }public final boolean isClosed(){return true;}private ClosedConnection(){}private static final ConnectionState _instance = new ClosedConnection();static ConnectionState instance(Connection conn, Pool pool){return _instance;}
    }private static class OpenConnection implements ConnectionState
    {
    private final Pool pool;
    private final Connection conn;
    public final ConnectionState close()
    {
    //对一个open connection的关闭,会把原始数据库连接返还到connection pool. 同时,该连接死亡。
    pool.closeConnection(conn);
    return ClosedConnection.instance(conn, pool);
    }public final Connection getOpenConnection()
    {
    return conn;
    }public final boolean isClosed()
    {
    return false;
    }OpenConnection(Connection conn, Pool pool)
    {
    this.conn = conn; this.pool = pool;
    }static ConnectionState instance(Connection conn, Pool pool)
    {
    return new OpenConnection(conn, pool);
    }
    }private ConnectionState state;//用静态的工厂方法,可以隐藏我们的实现类,以后,根据需要,我们可以方便地修改实现类,比如用内部类取代。
    //根据要修饰的Connection的状态,初始化PooledConnection
    public static Connection decorate(Connection conn, Pool pool) throws SQLException
    {
    if(conn.isClosed())
    {
    return new PooledConnection(ClosedConnection.instance(conn, pool));
    }
    else
    {
    return new PooledConnection(OpenConnection.instance(conn, pool));
    }
    }
    private PooledConnection(ConnectionState state)
    {
    this.state = state;
    } public final boolean isClosed()
    {
    return state.isClosed();
    }public final void close() throws SQLException
    {
    state = state.close();
    }private final Connection getOpenConnection() throws SQLException
    {
    return state.getOpenConnection();
    }/*****然后,做委托****/
    public final Statement createStatement() throws SQLException
    {
    return getOpenConnection().createStatement();
    }public final void rollback()throws SQLException
    {
    getOpenConnection().rollback();
    }//等等等等
    } 好,再来看看ConnectionPoolImpl怎样使用PooledConnection.public class ConnectionPoolImpl implements ConnectionPool
    {
    public synchronized Connection getConnection()
    {
    Connection ret;
    如果pool里有Connection
    从pool中去掉一个Connection conn;
    clients++;
    ret = conn;
    否则,如果clients<maxClients
    生成一个新的连接conn
    clients++;
    ret = conn;
    否则,wait(),直到pool中有空闲Connection //下面的这个匿名类实际上是个adapter pattern. J 
    return PooledConnection.decorate(ret, new PooledConnection.Pool
    {
    public void closeConnection(Connection conn)
    {
    ConnectionPoolImpl.this.closeConnection(conn);
    }
    }
    }
    //其他都和原来一样
    }这样,所有对ConnectionPool的实现,都可以在返回一个物理Connection之前,把它用PooledConnection封装一下。如此,代码得到了重用。ConnectionPool的实现者可以把主要精力放在怎样处理池的各种功能。而不是怎样包装Connection.
    世界真美好!不过。。万一,李四忘了用PooledConnection包装他的Connection怎么办?编译器不会报错,因为反正都是Connection类型。“你也太杞人忧天了吧?他忘了揍他不就得了?”哎,保不齐呀!人不是机器,总有犯错的时候,到时候揍他有啥用?还手疼呢。同学们,今天的家庭作业是:想办法让李四的健忘症不会影响我们的ConnectionPool大业。
      

  2.   

    从一个ConnectionPool的实现看Design Pattern的运用 (四)作者:ajoo好了,同学们,大家对上回的“李四猜想”有没有结果呀?我们的口号是?
    “没有蛀牙”!No! 是“用户至上”!既然用户有容易忘的可能,那就证明我们的工作做得不好。我们为什么非要用户做他们做不好或容易弄错的事呢?好吧,让我们知错就改:public interface ConnectionMan extends PooledConnection.Pool{
    //在这个interface里,我们不再要求程序员必须封装Connection, 他们只需要直接返回Connection对象。 实际上,程序员可以完全忘记封装这码事。
    //我们将对返回的对象进行封装。
    Connection getConnection()throws SQLException;
    void clear();
    void closeConnection(Connection conn);
    }//然后,我们用一下的decorator类对返回值进行封装public class ConnectionMan2ConnectionPool implements ConnectionPool{
    public final Connection getConnection()throws SQLException{
    return PooledConnection.decorate(man.getConnection(), man);
    }
    public final void clear(){
    man.clear();
    }
    private final ConnectionMan man;
    private ConnectionMan2ConnectionPool(ConnectionMan man){
    this.man = man;
    }
    public static ConnectionPool decorate(ConnectionMan man){
    return new ConnectionMan2ConnectionPool(man);
    }
    }这样,程序员只需要实现一个辅助interface ConnectionMan. 完全不要考虑封装Connection的事。然后再用我们的ConnectionMan2ConnectionPool类把它转换成ConnectionPool, 交给ConnectionPool的用户使用。耶!
    “那万一李四忘了用ConnectionMan2ConnectionPool转换怎么办?”呵呵,别忘了,编译器不是吃素的。用户期待ConnectionPool, 而李四只有ConnectionMan, 他想不转换也不行啊!什么?今天的家庭作业?
    啊,让你们家长写表扬信给ajoo老师。:)玩笑。如果那位能发现进一步refactor的地方,欢迎指出!
      

  3.   

    从一个ConnectionPool的实现看Design Pattern的运用 (五)作者:ajooOK, 现在我们已经把封装Connection的任务从ConnectionPool的开发者身上去掉了。他们只要实现一个辅助的ConnectionMan 接口,余下的事由PooledConnection类和ConnectionMan2ConnectionPool类来完成。下面,再让我们仔细地看一下ConnectionManImpl类:
    public class ConnectioManImpl implements ConnectionMan{
    public synchronized Connection getConnection(){
    Connection ret;
    如果pool里有Connection
    从pool中去掉一个Connection conn;
    clients++;
    ret = conn;
    否则,如果clients<maxClients
    conn = newConnection();
    clients++;
    ret = conn;
    否则,wait(),直到pool中有空闲Connection 
    return conn;
    }
    public synchronized void closeConnection(Connection conn){
    pool.add(conn);
    clients--;
    notify();
    }
    private Connection newConnection(){
    //使用用户名,密码,数据库url等等信息从DriverManager生成一个Connection
    }
    //必要的一些用户名,密码等建立connection的信息。
    }大家是否注意到了?ConnectionMan的实现者除了写pooling的算法,还要关心如何创建connection. 而这个创建connection的过程并不是总是一样的。我们可能从DriverManager生成Connection, 也可能从DataSource生成connection;可能用用户名,密码生成,也可能用connection string生成。
    同样的pooling逻辑,可能需要处理不同的生成Connection的方式, 同一种生成connection的方式又有可能需要不同的pooling逻辑。因此,把pooling逻辑和connection生成耦合在一起似乎不是一个好办法。那么如何解决这个问题呢?pooling算法中,确实需要在适当的时刻生成connection啊!“把ConnectionManImpl做成抽象类,然后要求每个子类覆盖newConnection()方法”。 资深程序员张三不屑地说。是啊,这确实是个直观又有效的方法。对同一个pooling算法,你只要subclass自己的子类,制定自己的connection生成,就可以重用父类的逻辑。这叫template method pattern.不过,说实话,个人很不喜欢这个pattern. 从此例来说,假如我们有五种pooling算法,三种connection生成方法,那我们就需要写十五个子类。太不灵活了。而且,实现继承造成的父子类的强耦合关系,也是我所向来讨厌的。父类的某个不经心的改变,有可能就使子类不再工作。那么。对啦!让我们抽象一下connection的生成吧。用abstract factory.先定义一个factory的接口。
    public interface ConnectionFactory{
    public Connection createConnection()throws SQLException;
    }
    然后改写我们的ConnectionManImpl, 让它把生成Connection的工作委托给一个ConnectionFactory.Public class ConnectionManImpl implements ConnectionMan{
    Private final ConnectionFactory factory;
    Private final int maxConn;
    private ConnectionManImpl(ConnectionFactory factory, int max){
    this.factory = factory;
    this.maxConn = max;
    }
    static public ConnectionMan instance(ConnectionFactory factory, int max){
    return new ConnectionManImpl(factory, max);
    }
    public final synchronized Connection getConnection()
    throws SQLException
    {
    如果pool里有Connection
    从pool中去掉一个Connection conn;
    clients++;
    return conn;
    否则,如果clients<maxClients
    conn = factory.createConnection();
    clients++;
    return conn;
    否则,wait(),直到pool中有空闲Connection 
    }
    //其他和前面一样。
    }再看一个示例ConnectionFactory的实现:
    public class ConnectionFactoryImpl
    {
    private ConnectionFactoryImpl(){}
    static public ConnectionFactory instance(final String user, final String pwd, 
    final String url, final String driver)
    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);
    }
    };

    }最后,再看看我们是怎样把一个ConnectionMan, 一个ConnectionFactory组合成一个ConnectionPool的。public class TestConnectionPool{
    public static void test(String user, String pwd, String url, String driver)
    throws java.sql.SQLException, ClassNotFoundException{
    final ConnectionPool pool = ConnectionMan2ConnectionPool.decorate(
    ConnectionManImpl.instance(
    ConnectionFactoryImpl.instance(user, pwd, url, driver),
    1000)
    );
    }
    }
    好啦,这一章,我们显示了怎样把ConnectionManImpl中的pooling逻辑和Connection 生成的逻辑分开,从而实现更大程度上的代码重用。思考题:
    pooling, 作为一种技术,并不只是应用于ConnectionPool, 其他如Thread pool以及任何一种需要一定开销创建的资源都可以应用这种技术。
    那么,我们怎样能够把一个pooling的算法重用给connection pool, thread pool等不同的pool呢?怎样才能说:“给我李四写的pooling算法,我要拿它来对我的线程进行缓冲。”?而不是说:“李四,你的connection pooling算法写的不错,能不能给我的thread pooling也写一个一样的?”
      

  4.   

    从一个ConnectionPool的实现看Design Pattern的运用 (六)作者:ajoo要对不同资源重用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
      

  5.   

    源代码见
    http://www.javaresearch.org/article/showarticle.jsp?column=31&thread=1253