OO隨筆(關(guān)于connection pool系列的補(bǔ)充,兼答bonmot)
說(shuō)起OO, 每個(gè)人都有每個(gè)人自己的見(jiàn)解。粗淺者如“obj.method的語(yǔ)法就是OO”;高深的則必侃“design pattern”.
今天我也來(lái)說(shuō)說(shuō)我的一孔之見(jiàn)。
什么是OO?
就是面向接口編程。無(wú)論你是用vtable, 或gp的function object, 或就是C的函數(shù)指針,正交分解也好,各種pattern也罷,都是面向接口編程思想的一種實(shí)現(xiàn)。
為什么要面向接口編程?
為了解耦。
什么是解耦?
就是把程序中互相不相關(guān)或有限相關(guān)的模塊分割開(kāi)來(lái)。就象收拾屋子,你希望把不同的東西放到不同的地方。把醬油和醋倒進(jìn)不同的瓶子里去。
這里,對(duì)完全不相關(guān)的功能,可以簡(jiǎn)單地分開(kāi)實(shí)現(xiàn)。
但事實(shí)上,很多情況下,不同模塊之間是有互相之間的關(guān)系的。這時(shí),就需要接口。用接口準(zhǔn)確定義模塊之間的關(guān)系。解耦前,兩個(gè)模塊之間共享所有信息(這個(gè)信息包括數(shù)據(jù),也包括各自的實(shí)現(xiàn)細(xì)節(jié))。解耦后,需要共享的信息被準(zhǔn)確地定義在接口中。同時(shí),信息的流向也被確定。
解耦的好處是什么呢?
首先,程序變得清晰了。
其次,不該暴露的實(shí)現(xiàn)細(xì)節(jié)被隱藏了。代碼的修改變?nèi)菀琢恕?BR>再次,結(jié)構(gòu)靈活了,通過(guò)靜態(tài)多態(tài)(function object)或動(dòng)態(tài)多態(tài)(vtable), 一個(gè)模塊可以和任意實(shí)現(xiàn)接口的模塊協(xié)作。原來(lái)類A只能與類B協(xié)作,解耦后可以和所有實(shí)現(xiàn)接口IB的類如B1, B2, ... 協(xié)作了。擴(kuò)展性大大增強(qiáng)。自然而然就代碼重用了。
編譯依賴也沒(méi)有了。你可以專心寫(xiě)和編譯一個(gè)模塊,不用等待其它模塊的完成。
調(diào)試容易了。只要模塊對(duì)一個(gè)接口調(diào)試成功,其它的接口也沒(méi)有問(wèn)題。于是,甚至可以用一個(gè)simple naive的實(shí)現(xiàn)該接口的dummy類來(lái)調(diào)試。(這點(diǎn),使用template的gp不適用)
那么解耦的壞處是什么呢?
接口的定義變得很關(guān)鍵。解耦就是隱藏一些信息,定義一些需要共享的信息。如果接口定義的不好,隱藏了不該隱藏的信息,那么對(duì)某些需要這些信息的復(fù)雜情況來(lái)說(shuō),這個(gè)解耦就失敗了。
而如果沒(méi)有隱藏一些應(yīng)該隱藏的信息,那么不該有的耦合仍然存在。
那么怎樣解耦,又怎樣定義接口呢?
這是一個(gè)純粹業(yè)務(wù)邏輯的思考過(guò)程。這里,對(duì)編程語(yǔ)言的知識(shí)變得無(wú)關(guān)緊要。事實(shí)上,只要精確掌握需求,嚴(yán)密地分析需求和模塊內(nèi)部子模塊之間的需求,任何一個(gè)會(huì)邏輯思考的人都可勝任這個(gè)工作。就象歌星鄭智化一樣,雖然不識(shí)譜,但一樣寫(xiě)歌,只不過(guò)最后要懂譜的人把歌紀(jì)錄下來(lái)。
解耦的原則很簡(jiǎn)單:精確定義需求,仔細(xì)分析需求。不要隱藏任何“需求”也許會(huì)需要的信息。不要放過(guò)任何“需求”明顯不需要的信息。
而對(duì)需求不清楚的情況,寧可錯(cuò)放一千,不能錯(cuò)殺一個(gè)。總而言之,決不能隱藏可能需要的信息。
不考慮重用,重用是解耦后的自然結(jié)果。不能倒因?yàn)楣?/P>
至于對(duì)這些原則的具體的運(yùn)用在前面幾篇的connection pool的文章里已經(jīng)有所體現(xiàn)了。
下面,我先針對(duì)bonmot對(duì)我的connection pool的例子的疑問(wèn)進(jìn)行回答。最后,再對(duì)bonmot的一個(gè)問(wèn)題給出我的解決的思路。
無(wú)關(guān)緊要的問(wèn)題:
1.ConnectionFactoryImpl也可以繼承方式實(shí)現(xiàn)ConnectionFactory
其實(shí),我最初的實(shí)現(xiàn),的確是ConnectoinFactoryImpl implements ConnectionFactory的,
但后來(lái),當(dāng)我overload了instance()函數(shù)之后,我發(fā)現(xiàn),這兩個(gè)函數(shù)返回的ConnectionFactory的實(shí)現(xiàn)類的代碼是不同的。于是,匿名類就誕生了。
這里,有一點(diǎn)值得吹噓的是,對(duì)構(gòu)造函數(shù)的隱藏,使得使用ConnectionFactoryImpl的客戶代碼對(duì)我的改動(dòng)完全不敏感。這也就是我為什么一直鼓吹要隱藏構(gòu)造函數(shù)的原因。
以下是這個(gè)類的實(shí)現(xiàn):
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)。也工作的很好。不過(guò),記得在哪篇文章里看到過(guò),在新的java language specification里,動(dòng)態(tài)加載的類有可能被垃圾回收。如果是這樣,那不麻煩啦?我好容易Class.forName()加載了driver類,好嘛!哪天jvm一高興給我回收啦!所以咱還是以防萬(wàn)一的好!
功能的問(wèn)題
1.ConnectionPooling是實(shí)現(xiàn)pooling的算法,其最基本的就是getConnection(),releaseConnection(Conn)
為什么不直接在ConnectionPool定義releaseConnection()方法,而要多一個(gè)interface ConnectionHome
首先,我的ConnectionPool接口是直接給用戶使用的。我在該文的第一章就提出,向用戶暴露releaseConnection(Connection)是不好的。你怎樣保證用戶沒(méi)有向oracle連接池中返回sql server連接?怎樣保證用戶不會(huì)把同一個(gè)連接向連接池返回兩次?已經(jīng)有Connection.close(), 用戶為什么要調(diào)用releaseConnection?
ConnectionHome接口是PooledConnection類定義的。PooledConnection作為一個(gè)封裝在物理Connection外的與pool協(xié)同工作的類,它需要知道怎樣返還一個(gè)物理Connection. ConnectionHome接口只定義了一個(gè)方法:void releaseConnection(Connection), 就是描述這一需求的產(chǎn)物。
2.事物總是對(duì)等的,F(xiàn)actory用于實(shí)現(xiàn)物理連接,同樣應(yīng)該負(fù)責(zé)關(guān)閉物理連接,而不應(yīng)該讓pooling算法關(guān)閉物理連接。另外,獲取與關(guān)閉connection應(yīng)該在一個(gè)接口中實(shí)現(xiàn),如果分成2個(gè)接口,就不能保證連接的實(shí)現(xiàn)一定對(duì)應(yīng)于關(guān)閉的實(shí)現(xiàn)。
即Factory是物理層,pool是cache層,client是應(yīng)用層。
首先,ConnectionPooling作為一個(gè)描述pooling算法的接口,它需要代表所有可能的pooling算法,所以,我們不能排除在某種pooling算法中,它會(huì)以一定的邏輯關(guān)閉物理數(shù)據(jù)庫(kù)連接。因此,pooling算法一定要可以在任何時(shí)候關(guān)閉這個(gè)連接。
至于是調(diào)用Connection.close(), 還是放一個(gè)closeConnection方法在ConnectionFactory中,讓我們先看看一些其它的factory的實(shí)現(xiàn)。
在COM中,IFactory的接口負(fù)責(zé)生產(chǎn)對(duì)象。但釋放對(duì)象是由IUnknown::Release()負(fù)責(zé)的。
在Java中,很多Factory接口負(fù)責(zé)生產(chǎn)對(duì)象,但垃圾收集負(fù)責(zé)回收對(duì)象。
為什么這些factory的機(jī)制不要求生產(chǎn)者來(lái)銷(xiāo)毀對(duì)象呢?
其原因在于類型安全!
舉個(gè)例子:
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();
}
/*丑啊!*/
}
}
在這樣一個(gè)工廠里,getObject方法知道生產(chǎn)的對(duì)象的真正類型。但在返回之后,該對(duì)象的真正類型就被丟失了。
這樣,如果你再把對(duì)象送還給工廠,說(shuō):“嘿!這是從你們廠出的,現(xiàn)在我不用了,還給你。”對(duì)工廠來(lái)說(shuō),它需要:
1, 確認(rèn)這個(gè)對(duì)象真是出自本廠。(這可不那么容易)
2, 確認(rèn)這個(gè)對(duì)象是怎么造出來(lái)的。以便找出相應(yīng)的銷(xiāo)毀機(jī)制(也不容易)
我們?yōu)槭裁床话裷eleaseConnection對(duì)用戶公開(kāi)?也是因?yàn)榭紤]到用戶可能會(huì)錯(cuò)誤返還非本廠生產(chǎn)的東西。
其實(shí),當(dāng)對(duì)象出廠之后,只有對(duì)象自己才知道怎樣銷(xiāo)毀自己。其它任何對(duì)象,包括生產(chǎn)者,都無(wú)能為力。
4.可靠性不夠。表現(xiàn)在:
a.pool的可靠性應(yīng)該與server的可靠性無(wú)關(guān),即database server或socket server可能由于某些原因重新啟動(dòng),但pool不應(yīng)該也要重新啟動(dòng)(比如一個(gè)pool存有不同server的connection),否則就跑出錯(cuò)誤。所以,pool因該檢查物理connection的連接狀態(tài)
怎么說(shuō)呢?這屬于ConnectionPool這個(gè)接口的語(yǔ)義。我們是否想讓我們的pool即使數(shù)據(jù)庫(kù)server崩潰了也能工作呢?
首先,這樣做是否有意義呢?如果數(shù)據(jù)庫(kù)server崩潰了,我們的Connection pool怎么補(bǔ)救呢?
其次,就算這樣是有意義的,它也是ConnectionPooling的邏輯。完全可以交給一個(gè)對(duì)此負(fù)責(zé)的ConnectionPooling處理。
b.pooled Connection可能由于一個(gè)client忘記關(guān)閉,而導(dǎo)致整個(gè)pool阻塞。所以,應(yīng)該對(duì)pooled Connection進(jìn)行監(jiān)控,對(duì)于超時(shí)的或其他invaild狀態(tài)的pooled connection強(qiáng)制回收。
這個(gè)問(wèn)題提的好!起初,我覺(jué)得這也只是另一個(gè)ConnectionPooling的邏輯。可以交給一個(gè)監(jiān)測(cè)已分配的連接使用情況的ConnectionPooling實(shí)現(xiàn)來(lái)處理。但仔細(xì)一想。這樣做是不好的。
首先,監(jiān)視連接的使用一定會(huì)需要在連接對(duì)象上記錄一些狀態(tài),象連接分配的時(shí)間,最近一次客戶使用該連接的時(shí)間等等。而ConnectionPooling的語(yǔ)義是返回pool里的物理連接,而由ConnectionPooling2Pool類來(lái)做封裝。這樣,ConnectionPooling的實(shí)現(xiàn)就很難紀(jì)錄必要的信息。當(dāng)然,ConnectionPooling也可以在返回物理連接前先做一個(gè)wrapper, 把信息紀(jì)錄在這個(gè)wrapper里。可是,這樣一來(lái),類型安全就得不到保障。在使用該wrapper時(shí),就要進(jìn)行downcast.
其次,監(jiān)視已分配連接和管理空閑連接之間到底有多大耦合呢?能否對(duì)它們解耦呢?經(jīng)過(guò)分析,我感覺(jué),答案是:不能。監(jiān)視已分配連接的算法理論上有可能需要知道空閑連接的一些信息,而反之也是一樣。而且,更討厭的是,它們之間所需要的信息量無(wú)法估計(jì),也就是說(shuō),對(duì)一些特定的算法,它們可能是完全的緊耦合。如果按這樣分析,這種ConnectionPool可能還得要求實(shí)現(xiàn)者直接實(shí)現(xiàn)ConnectionPool, 就象我們第三章里使用的方法,只能偶爾使用一些utility類,象PooledConnection之類。
不過(guò),雖然我們不能完全把監(jiān)視算法和分配算法分開(kāi)。但事實(shí)上很多監(jiān)視算法,分配算法確實(shí)是互不相關(guān)的。我們也許可以寫(xiě)一個(gè)框架,簡(jiǎn)化對(duì)這些互不相關(guān)的算法的實(shí)現(xiàn)。雖然對(duì)完全緊耦合的情況我們無(wú)能為力,但對(duì)多數(shù)普通的情況,我們還是可以有些作為的。而且,這樣一個(gè)框架并不影響對(duì)復(fù)雜的緊耦合情況的特殊實(shí)現(xiàn)。
這個(gè)框架,當(dāng)然應(yīng)該和我們現(xiàn)有的框架協(xié)同工作。具體的實(shí)現(xiàn)思路,我將在后面給出。
c.ConnectionPoolingImpl
public final synchronized void clear(){
closeAll();
freeConn.removeAllElements();
}//沒(méi)有transaction保證,有可能引起數(shù)據(jù)不一致,資源(connection)泄漏(connection沒(méi)關(guān)閉,pool卻拿掉了)
可以關(guān)閉一個(gè)connection,去掉一個(gè)pool對(duì)象
這里不需要transaction保證的。我們先關(guān)閉所有連接,然后再清連接池,怎么可能“connection沒(méi)關(guān)閉,pool卻拿掉了”呢?
擴(kuò)展的問(wèn)題
1.ConnectionPool是否定義成一個(gè)結(jié)構(gòu)interface更好,而讓pooling實(shí)現(xiàn)pooling算法。
pool可定義成Vector,Tree,...,負(fù)責(zé)存儲(chǔ)遍歷,而pooling負(fù)責(zé)check in,check out.
數(shù)據(jù)結(jié)構(gòu)和算法永遠(yuǎn)是緊耦合的。實(shí)際上,算法決定數(shù)據(jù)結(jié)構(gòu),不可能實(shí)現(xiàn)定義一個(gè)數(shù)據(jù)結(jié)構(gòu),然后強(qiáng)迫所有算法使用。即使是Collection, Iterator之類較抽象的結(jié)構(gòu)也不行。
2.可能有大型的pool,比如字庫(kù),因此有檢索問(wèn)題
這就是ConnectionPooling的實(shí)現(xiàn)者要?jiǎng)拥哪X筋了。我們的框架只定義語(yǔ)義和責(zé)任分工,并不牽扯這樣的實(shí)現(xiàn)細(xì)節(jié)。
2.更復(fù)雜的是可能每個(gè)connection上有多個(gè)引用,pooling要負(fù)責(zé)給client引用最少的那個(gè)connection.
這還是一個(gè)實(shí)現(xiàn)的細(xì)節(jié)。不過(guò)我想不出有什么理由我們會(huì)要不同客戶共享同一個(gè)連接。這是不安全的,不是嗎?
3.可能同一個(gè)pool存儲(chǔ)不同類型的對(duì)象,對(duì)不同對(duì)象的處理是否可用visitor模式。
還是ConnectionPool的實(shí)現(xiàn)者的事。