從一個ConnectionPool的實現看design pattern的運用 (六) (轉)

worldblog發表於2007-12-12
從一個ConnectionPool的實現看design pattern的運用 (六) (轉)[@more@]

從一個ConnectionPool的實現看design pattern的運用 ():namespace prefix = o ns = "urn:schemas--com::office" />

要對不同資源重用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的ReFactory, 來讓不同的ResourcePool重用它。

 

重用Pooling演算法是關鍵。而能否重用依賴於我們把所有與具體Resource相關的細節抽取出來。在此演算法中,什麼是與具體資源相關呢?資源建立自然是一個,我們已經用一個abstract factory把它抽取出來了。另一個跟具體資源相關的是資源的釋放。我們用close()方法釋放Connection. 但對於其它的資源,也許那是一個release()方法,destroy()方法,甚至是沒有任何方法。

 

所以,為了重用pooling演算法,我們還需要抽象資源釋放。一個ResourceCollector的介面應該能夠完成這樣的工作。(如果說,ConnectionFactory是abstract factory pattern, 為什麼GOF的書裡沒有一個針對於ResourceCollector的pattern呢?)

 

好了,在寫程式碼之前,還有一點需要澄清:

許多C++員認為,C++的GP是一個比OO更有用的技術。其實,GP中最主要的function 的思想,和OO中的面向介面只是一對孿生兄弟罷了。Template比目前的OO的主要優點在於:1,高。2,減少了很多型別強制轉換(downcast).

而許多Java程式設計師則認為, Java不需要GP, 因為所有GP能做的事,OO都能做。如GP可以有vector, Java則有Vector, 只要做些downcast就可以了。但他們忘記了,靜態型別檢查是Java這種強型別語言的原則,也是所有程式設計師應該盡力遵循的。靠downcast的程式是容易出錯的。所以,Generic Java (加入generics的Java) 已經是下一版Java的目標了。

 

說這些題外話的目的是,使用目前的Java語法,即使我們可以寫出可以重用的ResourcePool的來,它也是以犧牲程式的型別安全來保障的。使用者需要顯式地加入downcast來使用這個框架。

因此,既然我們反正也只是學術上的探討,讓我們在這裡使用類似Generic Java的語法,來使我們的程式看起來更美。

 

首先是ResourceFactory介面:

public interface ResourceFactory{

  R createResource();

}

ResourceCollector介面:

public interface ResourceCollector{

  void closeResource(R r);

}

ResourceMan介面,仍然是一個對實現者友好,但需要封裝才能交給客戶的介面。

public interface ResourceMan{

  R getResource()

  throws Throwable;

  void clear();

  void releaseResource(R r);

}

下面是一個使用我們的ConnectionManImpl的pooling邏輯的ResourceMan的實現。

public class ResourceManImpl implements ResourceMan{

  typedef ResourceMan Man;

  typedef ResourceFactory Factory;

  typedef ResourceCollector Collector;

 

  private final Factory factory;

  private final Collector collector;

    private int client;

    private final Vector freeList = new Vector();

    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

  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{

  public void closeResource(Connection conn){

    conn.close();

  }

  private ConnectionCollector(){}

  private static final test.res.ResourceCollector singleton = new ConnectionCollector();

  public static test.res.ResourceCollector 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 Resourcellector{

  public void collect(Collection rs);

}

 

修改後的ResourceManImpl的程式碼是這樣:

 

public class ResourceManImpl implements ResourceMan{

  typedef ResourceMan Man;

  typedef ResourceFactory Factory;

  typedef ResourcesCollector Collector;

 

  private final Factory factory;

  private final Collector collector;

    private int client;

    private final Vector freeList = new Vector();

    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

  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{

  private static final NopResourcesCollector singleton = new NopResourcesCollector();

  private NopResourcesCollector(){}

  public void collect(Collection rs){}

  public static ResourcesCollector instance(){

    return singleton;

  }

}

(題外話:這裡的NopResourcesCollector有點特別,它不是一個帶型別引數的類,相反,它是一個普通的類,但它卻實現了任意ResourcesCollector介面。

這種特殊的語法是C++ templateGeneric Java所不支援的。它是實驗室裡的一種“趣向”。它的作用是避免了多個singleton例項。對所有的資源R, 無論是Connection, Thread 還是其它什麼,都共享一個NopResourcesCollector. 這是安全的,因為不管怎麼說, NopResourcesCollector並不做任何事情。)

 

給Connection pool之類的普通得ResourcesCollector的實現是這樣:

public class LinearResourcesCollector implements ResourcesCollector{

  private final ResourceCollector c;

  private LinearResourcesCollector(ResourceCollector c){

    this.c = c;

  }

  public void collect(Collection rs){

    對Collection中的每個r

    c.collect(r);

 

  }

  public static ResourcesCollector instance(ResourceCollector c){

    return new LinearResourcesCollector(c);

  }

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991978/,如需轉載,請註明出處,否則將追究法律責任。

相關文章