codefans

          導航

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          統計

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          程序設計鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          OO隨筆(關于connection pool系列的補充,兼答bonmot) 選擇自 ajoo 的 Blog

          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的實現者的事。

           


          相關文章
          對該文的評論
          ajoo ( 2002-08-07)
          myan, 我想我可能是沒有明白你的意思。那個關于hdc的東西,就當我沒說吧。

          至于pattern與否。我總覺得pattern看看可以。用來和別人交流也不錯。
          但真正做東西時,往往是做完了之后才reverse-engineer, 發現:“啊,我這里用的是bridge嘛,那里用的是state..."
          對于你的gui框架。我想,我會本著“分析需求;用接口定義需求;解耦”的原則來設計。并且象我上一貼提到的剝皮般地
          進行逐層細分。這樣,即使你最后發現某一部分不合適,修改也會被局限在最小的范圍內。
          舉個例子,它就象通訊協議里的層:
          定義了網絡層,下面又可以定義鏈路層,物理層。但對于網絡層的用戶,其它兩層都是透明的,并不是必然的,或強迫的。如果實現者覺得好,完全可以繞過鏈路層,或再加幾層。無論如何更改,不影響網絡層的用戶。而遞歸地,對于鏈路層的用戶,物理層的實現又是透明的。

          當然,面向接口的OO, 并不適合對performance敏感到一兩個函數調用開銷都要計較的應用,aggregate, interface的使用,總會引入一定的開銷。

          bonmot ( 2002-08-05)
          myan,
          個人覺得,設計時結構性質的pattern或不是pattern的框架已經成形,refactor時某些實現的pattern會自然而然逐步顯現。

          ajoo,
          如果factory維持對產品的引用,肯定是可以回收的,只是這樣會增加factory的復雜度,是否好可以商榷。
          ar7_top ( 2002-08-04)
          學習
          waveless ( 2002-08-03)
          ajoo關于DC的那段我沒看懂,myan的意思應該是在用到jpeg2dib轉換
          的某個函數里面無法得到和窗口相關的HDC了。這里的hdc應該是個參
          數。HdbJpeg2Dib里的HDC是那兒來的?

          當然象jpeg2dib這樣的過程中理論上是不應該需要HDC做參數的,但
          是Windows中和DIB有關的很多函數都需要HDC。其實只是為了取得一
          些設置象調色板什么的,用GetDC(NULL)取個設備DC給它應該就可以。
          不一定非要是某一個窗口的DC。但是這需要先看懂它的代碼,并對
          Windows中有關DIB的函數很熟悉。這樣就失去用現成庫的意義了。
          ajoo ( 2002-08-03)
          myan:
          還不是很明白。如果Jpeg2Dib是一個接口:
          interface Jpeg2Dib{
             virtual Dib* convert(Jpeg* jpeg)=0;
          };
          難道你不能這樣實現?
          class HdbJpeg2Dib:public Jpeg2Dib{
             public:
             Dib* convert(Jpeg* jpeg){
                return ::convert_func(hdc, jpeg);
             }
             private:
             const HDC hdc;
          };

          bonmot:
          我覺得你沒有很明白面向接口的意思。為什么不要求保證ConnectionPool實現中一定引用ConnectionPooling接口呢?因為這樣做是不好的。
          面向接口的原則是:誰需要,就有誰定義。提供功能的類可以不定義接口。但需要功能的模塊一定要定義所需要的接口。
          我們的ConnectionPool是pool的用戶需要的。對于一個ConnectionPool的用戶來說,他并不關心你是用什么巧妙的方法實現的這個ConnectionPool, 只要它能完成所定義的操作,對用戶來說就可以。誰管你是直接實現的還是用的什么pattern? ConnectionPooling相對于ConnectionPool的用戶來說,只是實現細節。屬于解耦要隱藏的無關信息。
          所以沒有理由要把ConnectionPooling的細節公之于眾。

          也許你要說:應該強迫程序員在實現ConnectionPool的時候使用ConnectionPooling. 我覺得這種強迫是不應該的。無論你的實現方法多么巧妙,你永遠不應該把它強加于人。
          而且,現實世界的復雜性永遠是超過我們的想象的。無論你多么激動于自己天才的實現方法,都不要以為它可以解決所有問題。就象你提出的監測已分配的連接的使用,它就完全可能和ConnectionPool的實現緊耦合,而使得我們的bridge pattern (僅僅實現ConnectionPooling)無法有效實現需求。
          另外,即使我們的ConnectionPooling的方法真能解決所有ConnectionPool實現的需求,我們就可以強迫所有實現都用它嗎?如果忽然有人給了我們一個已經實現好的connection pool, 它是五年前設計的,完全沒有用我們的ConnectionPooling的方法,難道我們就不能使用它了嗎?為什么不能簡單地用一個adapter來用它呢?

          其實,我頂煩某些巨大的framework, 你要不就用它,享受它的好處,也忍受它的局限。要不就只能干脆拋開它。象MFC就是這樣。
          一個真正的面向接口的設計應該開放的,我的想象應該是這樣:
          首先,假設需要實現的系統的功能被定義在一系列接口中。ok, 這些接口就是需求,無論你怎樣實現。
          現在,我們要實現這些接口。在實現過程中,我們發現系統可以被拆成幾個獨立的模塊,(這里的“可以”,可以是我們對需求的分析,認為這幾個模塊可以自然被解耦,也可能是我們發現,雖然該系統理論上是個緊耦合,但對一些特定的場合,還是可以做一些分解以簡化實現)。這樣,幾套子需求被定義。系統被拆成幾個小系統,幾套子接口,以及組合小系統為大系統的邏輯。
          這里,對頂層接口來說,這些子系統,子接口,都不是必須的。它們只是“限于我們有限的知識和經驗所使用的一種我們覺得好的實現方法”。所以,如果以后有我們的設計所沒有預料到的情況,至少我們還可以直接繞過這層設計,不會影響頂層的接口。
          好,同樣的分析,分解,遞歸地在實現每個子系統時使用。直到設計者認為已分解的足夠細,或留到以后refactor時再說。
          在這一層一層的分解過程中,每一層對其上一層來說都不是必須的,并且是透明的。這樣,如果后來的實現者發現某一個子模塊設計得不能完全符合需要,都完全可以推倒重來。
          在這樣的一個開放式的設計中,沒有什么是強迫的。從最頂層的實現,到底層的某個小子模塊的實現,都不是神圣不可侵犯的。
          當然,系統設計的目標是要盡量避免將來對相對頂層的子系統設計更換的可能性。因為,越更換頂層,代價越大,除非,那只是一個用于demo或測試的dummy實現, 或一個其它legacy system的adapter。

          這里,設計者應是謙虛的,不是說:“我就是這樣設計的,想要靈活性?呵呵,我都預想到了,這里,這里,這里,都是我預留的供你cusomize的地方。什么?不夠?不可能!”
          而是說:“我的設計不一定就是最好的,如果你認為有更好的設計實現方法,或它不能滿足你的需要,你可以輕易地使用你自己的方法。我的任何一層你都可以替換成你的實現。”

          另外,用factory來釋放對象確實是不可行的。用template?template依賴于靜態類型。而factory方法的返回類型只能是一個。

          elm:
          我覺得你所描述的接口系統就象我說的mfc式的封閉式的系統。繁文縟節都規定好了,只能使用,或在實現預留好的地方做些修改。
          比如說:總經理告訴項目經理要上一個項目。項目經理必須先開動員會,再做項目報告,再做需求分析。。。。。。一系列的過程都定死了。
          而一個開放式的系統是:總經理告訴項目經理要上一個項目,項目經理只負責最終實現這個項目,給出所有需要的deliverables.
          對項目經理來說,它可以是用上面描述的方法來組織項目。但那并不是公司要求的。如果需要,完全可以換個項目經理,引入一個新的方法。

          myan:
          我倒沒感覺你的那個gui系統就一定是composition或其它的任何一種pattern. :)
          我覺得,用上面說的那種撥皮式的方法,即使你最后要修改實現,應該也不是個很大的工程。為什么上來就直奔pattern而去呢?
           

          posted on 2005-11-22 11:52 春雷的博客 閱讀(284) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 唐海县| 朝阳县| 山阳县| 合水县| 资源县| 漠河县| 明光市| 瑞金市| 益阳市| 乌兰县| 双城市| 宁都县| 崇州市| 来宾市| 平塘县| 龙里县| 永丰县| 永和县| 永年县| 镇远县| 浏阳市| 介休市| 收藏| 中卫市| 得荣县| 霍林郭勒市| 会同县| 资溪县| 吴堡县| 海宁市| 绥阳县| 屏东市| 崇阳县| 丽江市| 图木舒克市| 綦江县| 洛扎县| 区。| 梁河县| 内丘县| 沛县|