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