http://firebody.blogbus.com/logs/2004/08/338549.html
轉(zhuǎn)自:javaeye論壇 作者:potian
說明:這些帖子都是在討論工廠模式,容器配置,構(gòu)造函數(shù)產(chǎn)生對象的方式各自優(yōu)缺時的回帖。很不錯!感謝potian!!!
除了靜態(tài)方法對具體子類的直接依賴問題之外,對象的產(chǎn)生封裝在對象內(nèi)部也是一個很奇怪的想法,如果在不同情況下,例如需要同步或者需要lazy等等的話,你必須針對修改你的代碼,實際上我們最關(guān)心的是能夠在不同場合使用同一個對象的業(yè)務(wù)邏輯,而你這樣做的話會緊緊因為我們需要不同的對象創(chuàng)建方法而修改對象的代碼,這是非常不智的,這也是singleton被視作evil的重要原因之一。(例如singleton,有時候我們希望使用超類的實例,有時候希望使用子類的實例,有的時候我們希望產(chǎn)生單個,有的時候需要產(chǎn)生多個[取singleton單控制點的含義],有的時候需要同步,有的時候不需要同步,在集群的情況我們甚至可能需要數(shù)據(jù)庫來實現(xiàn)唯一化控制,有時候希望緩存,有的時候不需要緩存,有的時候希望增強,有的時候希望采用動態(tài)代理產(chǎn)生。所以一般來說,我們希望在一個系統(tǒng)內(nèi)部最多只有一個入口的singleton,而我們也往往也不打算在其它不同的場合重用這個singleton)。
代碼的重用是對他業(yè)務(wù)邏輯的重用,這正是對象的核心價值。所以如同charon所說的,相對而言,我們往往不在乎組裝代碼的重用性,而是追求業(yè)務(wù)代碼本身可以被按照不同的組裝方式使用,例如既可以在EJB下,也可以在普通的Java應(yīng)用程序中,既可以作為遠程傳輸?shù)膶ο螅虼耍覀兺粫娭茖ο髽?gòu)造和產(chǎn)生的方式(例如由EJB容器產(chǎn)生,由IOC容器產(chǎn)生、有抽象工廠產(chǎn)生,或者直接由new產(chǎn)生),相反,是把對象的產(chǎn)生交給外部負責(zé),這樣才能達到在不同場合下對象最大的可重用性。說個簡單的,如果對象的構(gòu)造方法是私有的,那么現(xiàn)在的很多框架(例如hibernate,JavaBean,EJB等等你就根本不能使用了).這也是現(xiàn)在認為POJO比有特殊要求的對象更好的原因,因為任何一種框架和技術(shù)都可以自由選擇自己的方式來創(chuàng)建對象。
所有創(chuàng)建型設(shè)計模式和IoC容器的使用正是基于這樣的假設(shè)的,只有對象把構(gòu)造的責(zé)任交給外部來實現(xiàn),那么我們才能有效地隱藏對象創(chuàng)建的時機、方式、方法,給不修改對象本身的代碼而能夠使用不同的對象創(chuàng)建方式(包括使用子類,替代類)提供了前提,從而提高對象實現(xiàn)業(yè)務(wù)邏輯的在不同場合的可重用性。所以不是說一定要用IOC容器或者用抽象工廠,而是可以用這些方法,也可以不用這種方法。而就抽象工廠和IoC容器本身而言,通過它們各自的封裝,可以進一步實現(xiàn)對不同具體實現(xiàn)子類的解綁,就更加好了。如果有一天你不想用Pico或者不想用抽象工廠,你可以選擇其他更加合適的方法來實現(xiàn)解綁。而這個責(zé)任不應(yīng)該交給對象自己來實現(xiàn),因為我們根本無法預(yù)料這個對象將會以什么樣的方式被構(gòu)造出來,在什么時候構(gòu)造出來,需要依賴什么其它外部機制構(gòu)造出來(例如可能依賴數(shù)據(jù)庫,或者依賴串行化),這最好是有外部使用這個對象的環(huán)境來決定
最終,我們希望能夠讓對象的創(chuàng)建、對象的銷毀完全脫離對象的使用,垃圾收集器已經(jīng)為我們提供VM級別的支持,而抽象工廠和工廠方法以及其它構(gòu)造型設(shè)計模式,IoC從模式和框架的角度給我們另一半。
你這個已經(jīng)是每次返回一個新的對象了,我要每次得到同一對象,我該怎么辦,你是不是叫我重新再在上面包一個類,把第一次取到的對象緩存起來,以后每次去取那個對象?這個時候你每次new一個還有什么意義,你說可以把它改成單個,那我原先在用的代碼怎么辦?
再復(fù)雜一點,如果是需要在集群的服務(wù)器之間保持singleton,有的時候我需要用數(shù)據(jù)庫來持久對象狀態(tài),通過數(shù)據(jù)庫來保持唯一性,那你這份代碼需要依賴于JDBC,這個時候如果一個普通的應(yīng)用程序是不是也要依賴于JDBC?
區(qū)別在于你的代碼是放在本類里面的,你這個類構(gòu)造會直接依賴于你的環(huán)境和當(dāng)前的假設(shè),別人根本沒辦法重用你的類
構(gòu)造函數(shù)為什么沒有這些缺陷,因為我們可以通過另外一個類來實現(xiàn)和不同環(huán)境的結(jié)合,而我們正在談?wù)摰倪@個類本身可以在任何地方使用而不需要修改任何代碼.這是非常重要的,這是重用的基礎(chǔ),從我們談的范圍內(nèi)來說,如果對照OCP,那么它是C,一旦關(guān)閉,永不修改。構(gòu)造函數(shù)為什么沒有這個問題,因為他自己不對任何構(gòu)造自己的方法作出額外的假設(shè)(構(gòu)造函數(shù)是最最近基本的假設(shè)了),而把構(gòu)造的方法、意圖和時機完全交給了外部,如果對照OCP,那么它是O。除非你的靜態(tài)方法永遠等同于new,不然的話,你任何一種實現(xiàn)方式都是對可能的構(gòu)造方式進行無意義的假設(shè),會限制其他場合對你這份代碼重用的可能性,而如果永遠等于new,那你這個靜態(tài)方法還有什么用。
舉例來說,假設(shè)有3個應(yīng)用,對一個對象需要三種不同的構(gòu)造方法,一個需要proxy以實現(xiàn)攔截,一個需要普通的java類做測試,另一個需要從數(shù)據(jù)庫里面讀取以保持集群之間的唯一性,我只需要對不同的環(huán)境寫不同的工廠,而不需要去改你那個嵌在業(yè)務(wù)代碼中的靜態(tài)方法,即使改了也不能同時重用于三個場合,你這個時候告訴我這是外部設(shè)計的決策. 和你的實現(xiàn)細節(jié)無關(guān),我可以在外面去包一個類,那我要你那個靜態(tài)方法干什么,這個靜態(tài)方法里面到底是返回proxy還是普通java對象還是從數(shù)據(jù)庫里面讀取,任何一個都沒有意義
總而言之,任何在本類內(nèi)部假設(shè)自己將被如何、何時、以何種方式進行構(gòu)造的代碼是極其不利于重用的(例如singleton就是一種限制重用的方法,只不過它由他自己適合的環(huán)境,因為任何系統(tǒng)里面肯定有一些類是可以重用,而有一些是特定于某個應(yīng)用的)。因為你適合了一種環(huán)境的構(gòu)造,必然會產(chǎn)生對另一種狀況的不適合,所以最好的方式是什么也不假設(shè),把如何構(gòu)造的責(zé)任傳遞給另外一個類或者框架,那個類可以結(jié)合具體的重用場合實現(xiàn)構(gòu)造,緩存,單件化,動態(tài)代理等等它希望做的任何動作。
面向?qū)ο蟮闹行狞c是職責(zé)分離和變化頻率的分離,對一個希望被重用的對象來說,它的業(yè)務(wù)代碼是它變化頻率較低的部分(這是重用的基本假設(shè)),而由于目前各種不用容器、測試、分布式計算、事務(wù)處理等等場合的需要,它如何被構(gòu)造的可能性則是一個比它本身業(yè)務(wù)邏輯變化率高得多的東西,這兩種職責(zé)必須被分離。
至于你這種設(shè)計方法完全依賴于子類和繼承上的困難就更不用談了。你說你的大多數(shù)類不讓繼承,OO最重要的概念就是差異編程和增量編程,這是提高內(nèi)外部質(zhì)量、提高生產(chǎn)率的核心思想,如果沒有記錯的話,《面向?qū)ο筌浖?gòu)造》第一章里面就明確地提出了幾個重要的內(nèi)外部指標(biāo),也是整個OO思想的軟件工程基礎(chǔ)。