一.問(wèn)題的提出
我常常在思考一個(gè)問(wèn)題,我們?nèi)绾文茉O(shè)計(jì)出高水平、高質(zhì)量的軟件出來(lái)。怎樣是高水平、高質(zhì)量的軟件?它應(yīng)當(dāng)是易于維護(hù)、易于適應(yīng)變更、可重用性好的一個(gè)系統(tǒng)。如何做到這一點(diǎn)呢?答案當(dāng)然是“低耦合、高內(nèi)聚”了。低耦合就是軟件在構(gòu)造的時(shí)候,各個(gè)模塊、各個(gè)功能、各個(gè)類(lèi)都不會(huì)過(guò)度依賴于它周?chē)沫h(huán)境。只有這樣,才能使我們的模塊(功能、類(lèi))在周?chē)l(fā)生變更時(shí)不受影響,做到易于維護(hù)和易于適應(yīng)變更。正因?yàn)槿绱耍彩顾子谥赜玫狡渌δ茴?lèi)似的環(huán)境中,提高了重用性。高內(nèi)聚則使軟件中的各個(gè)模塊(功能、類(lèi))能夠各盡其能而又充分合作,也就是對(duì)于軟件問(wèn)題空間中需求的各個(gè)功能,系統(tǒng)可以合理地把它分配給各個(gè)模塊(功能、類(lèi))來(lái)共同完成,而不是一個(gè)或幾個(gè)八面玲瓏、包打天下的超級(jí)類(lèi)一個(gè)人完成。而對(duì)于該系統(tǒng)中的某一個(gè)模塊(功能、類(lèi)),具有自己高度相關(guān)的職責(zé),即該職責(zé)中的幾個(gè)任務(wù)是高度相關(guān)的。每一個(gè)模塊(功能、類(lèi))都決不去完成與自己無(wú)關(guān)職責(zé)的任務(wù)。
那么怎樣能構(gòu)造一個(gè)低耦合、高內(nèi)聚的系統(tǒng)呢,時(shí)下最流行的框架結(jié)構(gòu)之一的struts+spring+hibernate為我們提供了方便。使用struts我們可以應(yīng)用MVC模型,使頁(yè)面展現(xiàn)與業(yè)務(wù)邏輯分離,做到了頁(yè)面展現(xiàn)與業(yè)務(wù)邏輯的低耦合。當(dāng)我們的頁(yè)面展現(xiàn)需要變更時(shí),我們只需要修改我們的頁(yè)面,而不影響我們的業(yè)務(wù)邏輯;同樣,我們的業(yè)務(wù)邏輯需要變更的時(shí)候,我們只需要修改我們的java程序,與我們的頁(yè)面無(wú)關(guān)。使用spring我們運(yùn)用IoC(反向控制),降低了業(yè)務(wù)邏輯中各個(gè)類(lèi)的相互依賴。假如類(lèi)A因?yàn)樾枰δ蹻而調(diào)用類(lèi)B,在通常的情況下類(lèi)A需要引用類(lèi)B,因而類(lèi)A就依賴于類(lèi)B了,也就是說(shuō)當(dāng)類(lèi)B不存在的時(shí)候類(lèi)A就無(wú)法使用了。使用了IoC,類(lèi)A調(diào)用的僅僅是實(shí)現(xiàn)了功能F的接口的某個(gè)類(lèi),這個(gè)類(lèi)可能是類(lèi)B,也可能是另一個(gè)類(lèi)C,由spring的配置文件來(lái)決定。這樣,類(lèi)A就不再依賴于類(lèi)B了,耦合度降低,重用性提高了。使用hibernate則是使我們的業(yè)務(wù)邏輯與數(shù)據(jù)持久化分離,也就是與將數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)的操作分離。我們?cè)跇I(yè)務(wù)邏輯中只需要將數(shù)據(jù)放到值對(duì)象中,然后交給hibernate,或者從hibernate那里得到值對(duì)象。至于用Oracle、MySQL還是SQL Server,如何執(zhí)行的操作,與我無(wú)關(guān)。
然而我要說(shuō)的是,即使我們使用了struts+spring+hibernate框架構(gòu)建我們的軟件,就可以做到“低耦合、高內(nèi)聚”了嗎?我認(rèn)為這是遠(yuǎn)遠(yuǎn)不夠的!我認(rèn)為我們?cè)谑褂胹truts+spring+hibernate框架的時(shí)候常常會(huì)有以下幾個(gè)問(wèn)題值得改進(jìn)。
二.分析與解決
1.編寫(xiě)DAO的時(shí)候不要直接去使用hibernate或spring對(duì)hibernate的支持。
現(xiàn)在我們?cè)诰帉?xiě)DAO的時(shí)候普遍都是直接繼承spring對(duì)hibernate的封裝類(lèi)HibernateDaoSupport,然后使用該類(lèi)提供的諸如saveOrUpdate(), saveOrUpdateCopy(), find()等等。另外,在使用excute()方法實(shí)現(xiàn)一些更復(fù)雜的hibernate功能的時(shí)候還會(huì)使用hibernate的類(lèi),諸如Query, Session, Type等。這樣直接使用spring和hibernate的類(lèi)存在的問(wèn)題在于,你的代碼將不得不依賴與spring和hibernate的某個(gè)版本。比如說(shuō),現(xiàn)在hibernate3出來(lái)了,改動(dòng)挺大,實(shí)際上最要命的是包結(jié)構(gòu),hibernate2的包結(jié)構(gòu)是net.sf.hibernate.*,然而hibernate3是org.hibernate.*。同樣,spring為了支持hibernate3,包名也改為org.springframework.orm.hibernate3.*。假如,你現(xiàn)在新開(kāi)發(fā)一個(gè)項(xiàng)目,這沒(méi)什么關(guān)系,如果是升級(jí)一個(gè)項(xiàng)目問(wèn)題就來(lái)了。如果你希望將你的一個(gè)項(xiàng)目從hibernate2升級(jí)為hibernate3,你不得不修改DAO中所有對(duì)hibernate和spring-hibernate的引用。如果你的代碼中出現(xiàn)hibernate2與hibernate3不兼容的方法和類(lèi),比如saveOrUpdateCopy()(在hibernate3中已經(jīng)沒(méi)有了),你還將不得不改寫(xiě)。那么你可能會(huì)說(shuō),我不會(huì)這樣升級(jí)。如果你的軟件生命周期有好多年,hibernate升級(jí)到4,升級(jí)到5,你還是依然使用hibernate2?如果你以這種方式開(kāi)發(fā)一個(gè)平臺(tái),你能要求所有使用你平臺(tái)的軟件項(xiàng)目都只能使用hibernate2?更進(jìn)一步說(shuō),我現(xiàn)在開(kāi)發(fā)一個(gè)產(chǎn)品,今后的客戶將是成千上萬(wàn)。經(jīng)過(guò)1、2年我需要升級(jí)了,這時(shí)我的升級(jí)包有幾十M,幾乎把所有的DAO都換了個(gè)遍,這樣的升級(jí)無(wú)異于重裝。也許,有人會(huì)提出另一個(gè)方案,在HibernateDaoSupport與DAO中間增加了一個(gè)基礎(chǔ)類(lèi),這樣將基礎(chǔ)類(lèi)中的org.springframework.orm.hibernate.support.HibernateDaoSupport,改為了org.springframework.orm.hibernate3.support.HibernateDaoSupport,這樣其下面繼承的DAO就不用改動(dòng)了。然而在源碼上是小小的改動(dòng),但對(duì)于類(lèi)來(lái)說(shuō),兩個(gè)不同版本的HibernateDaoSupport其相關(guān)的屬性和方法還是有不少變化,那么在基礎(chǔ)類(lèi)重新編譯的同時(shí),你的繼承類(lèi)重新編譯否。既然已經(jīng)重新編譯了,因此你的所有DAO在升級(jí)的時(shí)候依然要打入升級(jí)包,問(wèn)題依然存在。
以上問(wèn)題,究其原因,是我們項(xiàng)目中的DAO依賴于hibernate和spring,因?yàn)槲覀儗?duì)它們的使用是繼承,是一種很強(qiáng)的關(guān)聯(lián),就是一種依賴。我們只需要稍微進(jìn)行一些調(diào)整,就可以解決這個(gè)問(wèn)題,那就是不使用直接繼承,而使用接口進(jìn)行分離。可以使用Fa?ade模式,先建立一個(gè)叫BasicDao的基礎(chǔ)類(lèi),從名稱我們可以看出,它是所有DAO的基礎(chǔ)類(lèi),實(shí)現(xiàn)DAO操作所需的所有諸如save()、delete()、load()、query()等方法,除了一些基本的方法,諸如翻頁(yè)查詢、getCount、解析查詢條件形成HQL語(yǔ)句等功能也在這里實(shí)現(xiàn),但是不要使用與hibernate或spring有關(guān)的任何方法和類(lèi)。同時(shí),BasicDao調(diào)用一個(gè)叫DaoSupport的接口,DaoSupport的接口則是提供持久化所需的基本方法,最原始的元素。然后,我為DaoSupport接口提供各種不同的實(shí)現(xiàn),比如hibernate2的實(shí)現(xiàn)DaoSupportHibernateImp、hibernate3的實(shí)現(xiàn)DaoSupportHibernate3Imp,整個(gè)結(jié)構(gòu)如下圖所示。BasicDao可以使用hibernate或spring提供的方法,但是不是直接使用,而是通過(guò)調(diào)用DaoSupport的實(shí)現(xiàn)類(lèi)來(lái)使用。然而B(niǎo)asicDao到底是使用的那個(gè)實(shí)現(xiàn)類(lèi),我們通過(guò)spring的IoC,通過(guò)配置文件來(lái)決定到底使用哪個(gè)實(shí)現(xiàn)。同時(shí),BasicDao也不要使用諸如SpringContext的類(lèi)來(lái)實(shí)現(xiàn)IoC,而是通過(guò)建立setDaoSupport()和getDaoSupport()方法,然后在spring配置文件中建立引用。
2.編寫(xiě)Action的時(shí)候不要直接使用spring和spring的繼承類(lèi)
前面我說(shuō)了應(yīng)當(dāng)避免DAO引用spring或hibernate及其繼承類(lèi)。同樣的事情也發(fā)生在Action中。由于Action通常不納入spring的管理,因此Action在通過(guò)spring調(diào)用某個(gè)BUS的時(shí)候,往往是去引用一個(gè)叫SpringContext的類(lèi)(spring的類(lèi)ContextLoaderServlet的繼承類(lèi)),然后使用它的getBean()方法。如此的使用,我們的Action將依賴與spring。我們同樣可以使用一個(gè)叫BasicAction的父類(lèi),然后用一個(gè)接口來(lái)隔離spring。由于Action通常不納入spring的管理,我們通過(guò)一個(gè)*.property的配置文件來(lái)決定接口到底調(diào)用哪個(gè)實(shí)現(xiàn)類(lèi)。這樣的結(jié)構(gòu)的另一個(gè)好處是,我們還可以將所有Action都必須使用的諸如寫(xiě)日志、用戶校驗(yàn)、異常處理都放在父類(lèi)BasicAction中,提高系統(tǒng)的可維護(hù)性。
3.當(dāng)BUS需要獲取別的模塊的數(shù)據(jù)的時(shí)候,不要直接去使用該模塊的DAO
我舉一個(gè)簡(jiǎn)單的例子:我需要設(shè)計(jì)一個(gè)軟件評(píng)審的管理軟件,該軟件分為評(píng)審組織者制訂評(píng)審計(jì)劃、評(píng)審者分別填寫(xiě)評(píng)審表后由評(píng)審組織者匯總評(píng)審表、評(píng)審組織者制作評(píng)審報(bào)告。這是一個(gè)非常簡(jiǎn)單的項(xiàng)目,分成了三個(gè)人來(lái)完成。但是項(xiàng)目進(jìn)行快結(jié)束的時(shí)候卻出現(xiàn)了問(wèn)題。填寫(xiě)評(píng)審表需要獲得評(píng)審計(jì)劃中的一些數(shù)據(jù),制作評(píng)審報(bào)告的數(shù)據(jù)來(lái)源于評(píng)審表。項(xiàng)目組在開(kāi)始編程前先開(kāi)了一次會(huì),大家約定好了各個(gè)部分的數(shù)據(jù)格式及其規(guī)則,然后開(kāi)始工作。然而數(shù)天后項(xiàng)目組把各個(gè)模塊整合以后發(fā)現(xiàn),系統(tǒng)根本跑不起來(lái),為什么呢?設(shè)計(jì)評(píng)審計(jì)劃的人發(fā)現(xiàn),所有評(píng)審計(jì)劃應(yīng)當(dāng)按照產(chǎn)品編號(hào)來(lái)進(jìn)行管理而不是項(xiàng)目編號(hào)。由于這個(gè)變更,填寫(xiě)評(píng)審表模塊在待評(píng)審列表中什么都無(wú)法顯示;同樣,設(shè)計(jì)評(píng)審表的人發(fā)現(xiàn),在一個(gè)評(píng)審計(jì)劃中評(píng)審表與評(píng)審者不是一對(duì)多的關(guān)系,而是一對(duì)一的關(guān)系,因而修改了這兩個(gè)表的關(guān)聯(lián)。因?yàn)檫@樣,在制作評(píng)審報(bào)告時(shí)就不能正確得到評(píng)審表數(shù)據(jù)。其實(shí)一個(gè)軟件項(xiàng)目在整個(gè)進(jìn)行過(guò)程中總是不斷變更。我們需要做的不是去抑制這些變更,而應(yīng)當(dāng)是通過(guò)軟件的結(jié)構(gòu)去適應(yīng)這些變更,即是降低各模塊間的依賴(耦合),提高內(nèi)聚。
拿這個(gè)實(shí)例來(lái)說(shuō),當(dāng)評(píng)審表需要調(diào)用評(píng)審計(jì)劃的數(shù)據(jù)的時(shí)候,不應(yīng)當(dāng)是自己寫(xiě)一個(gè)DAO去調(diào)用評(píng)審計(jì)劃的數(shù)據(jù),而應(yīng)當(dāng)是調(diào)用評(píng)審計(jì)劃的接口,將這個(gè)任務(wù)交給評(píng)審計(jì)劃類(lèi)來(lái)完成。當(dāng)評(píng)審報(bào)告需要調(diào)用評(píng)審表的數(shù)據(jù)的時(shí)候,同樣應(yīng)當(dāng)去調(diào)用評(píng)審表的接口,由評(píng)審表來(lái)實(shí)現(xiàn)。同時(shí),這種調(diào)用應(yīng)當(dāng)是去調(diào)用BUS層的接口。為什么呢?比如在評(píng)審計(jì)劃中的一個(gè)業(yè)務(wù)邏輯是只有在評(píng)審計(jì)劃發(fā)布以后才能制作評(píng)審表,那么怎樣才是已發(fā)布的評(píng)審計(jì)劃呢?這個(gè)業(yè)務(wù)邏輯應(yīng)當(dāng)由誰(shuí)來(lái)定義?當(dāng)然是評(píng)審計(jì)劃。在什么地方定義?當(dāng)然是BUS而不是DAO,因?yàn)镈AO僅僅是實(shí)現(xiàn)數(shù)據(jù)的持久化,而B(niǎo)US才是實(shí)現(xiàn)業(yè)務(wù)邏輯的地方。既然如此,如果評(píng)審表去調(diào)用評(píng)審計(jì)劃的DAO,那么已發(fā)布評(píng)審計(jì)劃的業(yè)務(wù)邏輯必然包含在了評(píng)審表的業(yè)務(wù)邏輯里了。我們假設(shè)有一天,已發(fā)布評(píng)審計(jì)劃的業(yè)務(wù)邏輯發(fā)生變更了(實(shí)際上這樣的會(huì)在你毫不經(jīng)意間就發(fā)生了),編寫(xiě)評(píng)審計(jì)劃的人會(huì)很快就修改了評(píng)審計(jì)劃的業(yè)務(wù)實(shí)現(xiàn)并且測(cè)試通過(guò)了。他不知道評(píng)審表里也包含了這樣的業(yè)務(wù)邏輯,因而修改后的程序在運(yùn)行到評(píng)審表的時(shí)候就很可能會(huì)出錯(cuò)。不幸的是,在實(shí)際工作中,同樣一個(gè)業(yè)務(wù)邏輯可能包含在無(wú)數(shù)個(gè)你可能知道,但你也可能不知道的代碼中。這樣的結(jié)構(gòu)就是一個(gè)不易于維護(hù)的差的結(jié)構(gòu)。
三.總結(jié):從技術(shù)升級(jí)和需求變更兩方面適應(yīng)變化
軟件開(kāi)發(fā)專(zhuān)家Alistair Cockburn在《敏捷軟件開(kāi)發(fā)》中說(shuō)過(guò),軟件在整個(gè)生命周期中變更是無(wú)時(shí)無(wú)刻不發(fā)生的。我認(rèn)為,軟件的變更一方面是技術(shù)的更新,今天我們使用struts+spring+hibernate,明天呢,我們將使用什么呢?正因?yàn)榧夹g(shù)變更得太快,我們的系統(tǒng)應(yīng)當(dāng)不要太依賴于某個(gè)具體的技術(shù)或框架,以便于明天的技術(shù)更新。同時(shí),來(lái)自客戶的需求變更也是我們必須面對(duì)的另一個(gè)壓力。一句經(jīng)典的話是這樣描述客戶的變更:“當(dāng)我看到時(shí)我的需求就變更了。(I changed just when I saw it.)”過(guò)去我們用需求說(shuō)明書(shū)來(lái)抑制用戶的變更,現(xiàn)在發(fā)現(xiàn)不能這樣了。敏捷軟件開(kāi)發(fā)提出了許多應(yīng)對(duì)用戶變更的辦法,其中建立低耦合高內(nèi)聚的軟件結(jié)構(gòu)也是辦法之一。系統(tǒng)中的所有對(duì)象都有自己的明確職責(zé),這個(gè)職責(zé)應(yīng)當(dāng)不多且高度相關(guān)。每個(gè)對(duì)象都應(yīng)當(dāng)只完成自己的職責(zé),而把其它的任務(wù)交給別人去做。正如我前面提到的例子,評(píng)審表對(duì)象只完成與評(píng)審表相關(guān)的操作,而在它需要完成的任務(wù)中,需要使用評(píng)審計(jì)劃數(shù)據(jù)的相關(guān)功能,交給評(píng)審計(jì)劃對(duì)象去完成,評(píng)審表只管調(diào)用。這樣的構(gòu)造要求開(kāi)發(fā)者相互協(xié)調(diào),彼此多交流,同時(shí),也需要有人來(lái)統(tǒng)一規(guī)劃,站在全局的設(shè)計(jì)這個(gè)系統(tǒng)。通過(guò)這些,我們才可以適應(yīng)變化,提高設(shè)計(jì)水平。
我常常在思考一個(gè)問(wèn)題,我們?nèi)绾文茉O(shè)計(jì)出高水平、高質(zhì)量的軟件出來(lái)。怎樣是高水平、高質(zhì)量的軟件?它應(yīng)當(dāng)是易于維護(hù)、易于適應(yīng)變更、可重用性好的一個(gè)系統(tǒng)。如何做到這一點(diǎn)呢?答案當(dāng)然是“低耦合、高內(nèi)聚”了。低耦合就是軟件在構(gòu)造的時(shí)候,各個(gè)模塊、各個(gè)功能、各個(gè)類(lèi)都不會(huì)過(guò)度依賴于它周?chē)沫h(huán)境。只有這樣,才能使我們的模塊(功能、類(lèi))在周?chē)l(fā)生變更時(shí)不受影響,做到易于維護(hù)和易于適應(yīng)變更。正因?yàn)槿绱耍彩顾子谥赜玫狡渌δ茴?lèi)似的環(huán)境中,提高了重用性。高內(nèi)聚則使軟件中的各個(gè)模塊(功能、類(lèi))能夠各盡其能而又充分合作,也就是對(duì)于軟件問(wèn)題空間中需求的各個(gè)功能,系統(tǒng)可以合理地把它分配給各個(gè)模塊(功能、類(lèi))來(lái)共同完成,而不是一個(gè)或幾個(gè)八面玲瓏、包打天下的超級(jí)類(lèi)一個(gè)人完成。而對(duì)于該系統(tǒng)中的某一個(gè)模塊(功能、類(lèi)),具有自己高度相關(guān)的職責(zé),即該職責(zé)中的幾個(gè)任務(wù)是高度相關(guān)的。每一個(gè)模塊(功能、類(lèi))都決不去完成與自己無(wú)關(guān)職責(zé)的任務(wù)。
那么怎樣能構(gòu)造一個(gè)低耦合、高內(nèi)聚的系統(tǒng)呢,時(shí)下最流行的框架結(jié)構(gòu)之一的struts+spring+hibernate為我們提供了方便。使用struts我們可以應(yīng)用MVC模型,使頁(yè)面展現(xiàn)與業(yè)務(wù)邏輯分離,做到了頁(yè)面展現(xiàn)與業(yè)務(wù)邏輯的低耦合。當(dāng)我們的頁(yè)面展現(xiàn)需要變更時(shí),我們只需要修改我們的頁(yè)面,而不影響我們的業(yè)務(wù)邏輯;同樣,我們的業(yè)務(wù)邏輯需要變更的時(shí)候,我們只需要修改我們的java程序,與我們的頁(yè)面無(wú)關(guān)。使用spring我們運(yùn)用IoC(反向控制),降低了業(yè)務(wù)邏輯中各個(gè)類(lèi)的相互依賴。假如類(lèi)A因?yàn)樾枰δ蹻而調(diào)用類(lèi)B,在通常的情況下類(lèi)A需要引用類(lèi)B,因而類(lèi)A就依賴于類(lèi)B了,也就是說(shuō)當(dāng)類(lèi)B不存在的時(shí)候類(lèi)A就無(wú)法使用了。使用了IoC,類(lèi)A調(diào)用的僅僅是實(shí)現(xiàn)了功能F的接口的某個(gè)類(lèi),這個(gè)類(lèi)可能是類(lèi)B,也可能是另一個(gè)類(lèi)C,由spring的配置文件來(lái)決定。這樣,類(lèi)A就不再依賴于類(lèi)B了,耦合度降低,重用性提高了。使用hibernate則是使我們的業(yè)務(wù)邏輯與數(shù)據(jù)持久化分離,也就是與將數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)的操作分離。我們?cè)跇I(yè)務(wù)邏輯中只需要將數(shù)據(jù)放到值對(duì)象中,然后交給hibernate,或者從hibernate那里得到值對(duì)象。至于用Oracle、MySQL還是SQL Server,如何執(zhí)行的操作,與我無(wú)關(guān)。
然而我要說(shuō)的是,即使我們使用了struts+spring+hibernate框架構(gòu)建我們的軟件,就可以做到“低耦合、高內(nèi)聚”了嗎?我認(rèn)為這是遠(yuǎn)遠(yuǎn)不夠的!我認(rèn)為我們?cè)谑褂胹truts+spring+hibernate框架的時(shí)候常常會(huì)有以下幾個(gè)問(wèn)題值得改進(jìn)。
二.分析與解決
1.編寫(xiě)DAO的時(shí)候不要直接去使用hibernate或spring對(duì)hibernate的支持。
現(xiàn)在我們?cè)诰帉?xiě)DAO的時(shí)候普遍都是直接繼承spring對(duì)hibernate的封裝類(lèi)HibernateDaoSupport,然后使用該類(lèi)提供的諸如saveOrUpdate(), saveOrUpdateCopy(), find()等等。另外,在使用excute()方法實(shí)現(xiàn)一些更復(fù)雜的hibernate功能的時(shí)候還會(huì)使用hibernate的類(lèi),諸如Query, Session, Type等。這樣直接使用spring和hibernate的類(lèi)存在的問(wèn)題在于,你的代碼將不得不依賴與spring和hibernate的某個(gè)版本。比如說(shuō),現(xiàn)在hibernate3出來(lái)了,改動(dòng)挺大,實(shí)際上最要命的是包結(jié)構(gòu),hibernate2的包結(jié)構(gòu)是net.sf.hibernate.*,然而hibernate3是org.hibernate.*。同樣,spring為了支持hibernate3,包名也改為org.springframework.orm.hibernate3.*。假如,你現(xiàn)在新開(kāi)發(fā)一個(gè)項(xiàng)目,這沒(méi)什么關(guān)系,如果是升級(jí)一個(gè)項(xiàng)目問(wèn)題就來(lái)了。如果你希望將你的一個(gè)項(xiàng)目從hibernate2升級(jí)為hibernate3,你不得不修改DAO中所有對(duì)hibernate和spring-hibernate的引用。如果你的代碼中出現(xiàn)hibernate2與hibernate3不兼容的方法和類(lèi),比如saveOrUpdateCopy()(在hibernate3中已經(jīng)沒(méi)有了),你還將不得不改寫(xiě)。那么你可能會(huì)說(shuō),我不會(huì)這樣升級(jí)。如果你的軟件生命周期有好多年,hibernate升級(jí)到4,升級(jí)到5,你還是依然使用hibernate2?如果你以這種方式開(kāi)發(fā)一個(gè)平臺(tái),你能要求所有使用你平臺(tái)的軟件項(xiàng)目都只能使用hibernate2?更進(jìn)一步說(shuō),我現(xiàn)在開(kāi)發(fā)一個(gè)產(chǎn)品,今后的客戶將是成千上萬(wàn)。經(jīng)過(guò)1、2年我需要升級(jí)了,這時(shí)我的升級(jí)包有幾十M,幾乎把所有的DAO都換了個(gè)遍,這樣的升級(jí)無(wú)異于重裝。也許,有人會(huì)提出另一個(gè)方案,在HibernateDaoSupport與DAO中間增加了一個(gè)基礎(chǔ)類(lèi),這樣將基礎(chǔ)類(lèi)中的org.springframework.orm.hibernate.support.HibernateDaoSupport,改為了org.springframework.orm.hibernate3.support.HibernateDaoSupport,這樣其下面繼承的DAO就不用改動(dòng)了。然而在源碼上是小小的改動(dòng),但對(duì)于類(lèi)來(lái)說(shuō),兩個(gè)不同版本的HibernateDaoSupport其相關(guān)的屬性和方法還是有不少變化,那么在基礎(chǔ)類(lèi)重新編譯的同時(shí),你的繼承類(lèi)重新編譯否。既然已經(jīng)重新編譯了,因此你的所有DAO在升級(jí)的時(shí)候依然要打入升級(jí)包,問(wèn)題依然存在。
以上問(wèn)題,究其原因,是我們項(xiàng)目中的DAO依賴于hibernate和spring,因?yàn)槲覀儗?duì)它們的使用是繼承,是一種很強(qiáng)的關(guān)聯(lián),就是一種依賴。我們只需要稍微進(jìn)行一些調(diào)整,就可以解決這個(gè)問(wèn)題,那就是不使用直接繼承,而使用接口進(jìn)行分離。可以使用Fa?ade模式,先建立一個(gè)叫BasicDao的基礎(chǔ)類(lèi),從名稱我們可以看出,它是所有DAO的基礎(chǔ)類(lèi),實(shí)現(xiàn)DAO操作所需的所有諸如save()、delete()、load()、query()等方法,除了一些基本的方法,諸如翻頁(yè)查詢、getCount、解析查詢條件形成HQL語(yǔ)句等功能也在這里實(shí)現(xiàn),但是不要使用與hibernate或spring有關(guān)的任何方法和類(lèi)。同時(shí),BasicDao調(diào)用一個(gè)叫DaoSupport的接口,DaoSupport的接口則是提供持久化所需的基本方法,最原始的元素。然后,我為DaoSupport接口提供各種不同的實(shí)現(xiàn),比如hibernate2的實(shí)現(xiàn)DaoSupportHibernateImp、hibernate3的實(shí)現(xiàn)DaoSupportHibernate3Imp,整個(gè)結(jié)構(gòu)如下圖所示。BasicDao可以使用hibernate或spring提供的方法,但是不是直接使用,而是通過(guò)調(diào)用DaoSupport的實(shí)現(xiàn)類(lèi)來(lái)使用。然而B(niǎo)asicDao到底是使用的那個(gè)實(shí)現(xiàn)類(lèi),我們通過(guò)spring的IoC,通過(guò)配置文件來(lái)決定到底使用哪個(gè)實(shí)現(xiàn)。同時(shí),BasicDao也不要使用諸如SpringContext的類(lèi)來(lái)實(shí)現(xiàn)IoC,而是通過(guò)建立setDaoSupport()和getDaoSupport()方法,然后在spring配置文件中建立引用。

2.編寫(xiě)Action的時(shí)候不要直接使用spring和spring的繼承類(lèi)
前面我說(shuō)了應(yīng)當(dāng)避免DAO引用spring或hibernate及其繼承類(lèi)。同樣的事情也發(fā)生在Action中。由于Action通常不納入spring的管理,因此Action在通過(guò)spring調(diào)用某個(gè)BUS的時(shí)候,往往是去引用一個(gè)叫SpringContext的類(lèi)(spring的類(lèi)ContextLoaderServlet的繼承類(lèi)),然后使用它的getBean()方法。如此的使用,我們的Action將依賴與spring。我們同樣可以使用一個(gè)叫BasicAction的父類(lèi),然后用一個(gè)接口來(lái)隔離spring。由于Action通常不納入spring的管理,我們通過(guò)一個(gè)*.property的配置文件來(lái)決定接口到底調(diào)用哪個(gè)實(shí)現(xiàn)類(lèi)。這樣的結(jié)構(gòu)的另一個(gè)好處是,我們還可以將所有Action都必須使用的諸如寫(xiě)日志、用戶校驗(yàn)、異常處理都放在父類(lèi)BasicAction中,提高系統(tǒng)的可維護(hù)性。
3.當(dāng)BUS需要獲取別的模塊的數(shù)據(jù)的時(shí)候,不要直接去使用該模塊的DAO
我舉一個(gè)簡(jiǎn)單的例子:我需要設(shè)計(jì)一個(gè)軟件評(píng)審的管理軟件,該軟件分為評(píng)審組織者制訂評(píng)審計(jì)劃、評(píng)審者分別填寫(xiě)評(píng)審表后由評(píng)審組織者匯總評(píng)審表、評(píng)審組織者制作評(píng)審報(bào)告。這是一個(gè)非常簡(jiǎn)單的項(xiàng)目,分成了三個(gè)人來(lái)完成。但是項(xiàng)目進(jìn)行快結(jié)束的時(shí)候卻出現(xiàn)了問(wèn)題。填寫(xiě)評(píng)審表需要獲得評(píng)審計(jì)劃中的一些數(shù)據(jù),制作評(píng)審報(bào)告的數(shù)據(jù)來(lái)源于評(píng)審表。項(xiàng)目組在開(kāi)始編程前先開(kāi)了一次會(huì),大家約定好了各個(gè)部分的數(shù)據(jù)格式及其規(guī)則,然后開(kāi)始工作。然而數(shù)天后項(xiàng)目組把各個(gè)模塊整合以后發(fā)現(xiàn),系統(tǒng)根本跑不起來(lái),為什么呢?設(shè)計(jì)評(píng)審計(jì)劃的人發(fā)現(xiàn),所有評(píng)審計(jì)劃應(yīng)當(dāng)按照產(chǎn)品編號(hào)來(lái)進(jìn)行管理而不是項(xiàng)目編號(hào)。由于這個(gè)變更,填寫(xiě)評(píng)審表模塊在待評(píng)審列表中什么都無(wú)法顯示;同樣,設(shè)計(jì)評(píng)審表的人發(fā)現(xiàn),在一個(gè)評(píng)審計(jì)劃中評(píng)審表與評(píng)審者不是一對(duì)多的關(guān)系,而是一對(duì)一的關(guān)系,因而修改了這兩個(gè)表的關(guān)聯(lián)。因?yàn)檫@樣,在制作評(píng)審報(bào)告時(shí)就不能正確得到評(píng)審表數(shù)據(jù)。其實(shí)一個(gè)軟件項(xiàng)目在整個(gè)進(jìn)行過(guò)程中總是不斷變更。我們需要做的不是去抑制這些變更,而應(yīng)當(dāng)是通過(guò)軟件的結(jié)構(gòu)去適應(yīng)這些變更,即是降低各模塊間的依賴(耦合),提高內(nèi)聚。
拿這個(gè)實(shí)例來(lái)說(shuō),當(dāng)評(píng)審表需要調(diào)用評(píng)審計(jì)劃的數(shù)據(jù)的時(shí)候,不應(yīng)當(dāng)是自己寫(xiě)一個(gè)DAO去調(diào)用評(píng)審計(jì)劃的數(shù)據(jù),而應(yīng)當(dāng)是調(diào)用評(píng)審計(jì)劃的接口,將這個(gè)任務(wù)交給評(píng)審計(jì)劃類(lèi)來(lái)完成。當(dāng)評(píng)審報(bào)告需要調(diào)用評(píng)審表的數(shù)據(jù)的時(shí)候,同樣應(yīng)當(dāng)去調(diào)用評(píng)審表的接口,由評(píng)審表來(lái)實(shí)現(xiàn)。同時(shí),這種調(diào)用應(yīng)當(dāng)是去調(diào)用BUS層的接口。為什么呢?比如在評(píng)審計(jì)劃中的一個(gè)業(yè)務(wù)邏輯是只有在評(píng)審計(jì)劃發(fā)布以后才能制作評(píng)審表,那么怎樣才是已發(fā)布的評(píng)審計(jì)劃呢?這個(gè)業(yè)務(wù)邏輯應(yīng)當(dāng)由誰(shuí)來(lái)定義?當(dāng)然是評(píng)審計(jì)劃。在什么地方定義?當(dāng)然是BUS而不是DAO,因?yàn)镈AO僅僅是實(shí)現(xiàn)數(shù)據(jù)的持久化,而B(niǎo)US才是實(shí)現(xiàn)業(yè)務(wù)邏輯的地方。既然如此,如果評(píng)審表去調(diào)用評(píng)審計(jì)劃的DAO,那么已發(fā)布評(píng)審計(jì)劃的業(yè)務(wù)邏輯必然包含在了評(píng)審表的業(yè)務(wù)邏輯里了。我們假設(shè)有一天,已發(fā)布評(píng)審計(jì)劃的業(yè)務(wù)邏輯發(fā)生變更了(實(shí)際上這樣的會(huì)在你毫不經(jīng)意間就發(fā)生了),編寫(xiě)評(píng)審計(jì)劃的人會(huì)很快就修改了評(píng)審計(jì)劃的業(yè)務(wù)實(shí)現(xiàn)并且測(cè)試通過(guò)了。他不知道評(píng)審表里也包含了這樣的業(yè)務(wù)邏輯,因而修改后的程序在運(yùn)行到評(píng)審表的時(shí)候就很可能會(huì)出錯(cuò)。不幸的是,在實(shí)際工作中,同樣一個(gè)業(yè)務(wù)邏輯可能包含在無(wú)數(shù)個(gè)你可能知道,但你也可能不知道的代碼中。這樣的結(jié)構(gòu)就是一個(gè)不易于維護(hù)的差的結(jié)構(gòu)。
三.總結(jié):從技術(shù)升級(jí)和需求變更兩方面適應(yīng)變化
軟件開(kāi)發(fā)專(zhuān)家Alistair Cockburn在《敏捷軟件開(kāi)發(fā)》中說(shuō)過(guò),軟件在整個(gè)生命周期中變更是無(wú)時(shí)無(wú)刻不發(fā)生的。我認(rèn)為,軟件的變更一方面是技術(shù)的更新,今天我們使用struts+spring+hibernate,明天呢,我們將使用什么呢?正因?yàn)榧夹g(shù)變更得太快,我們的系統(tǒng)應(yīng)當(dāng)不要太依賴于某個(gè)具體的技術(shù)或框架,以便于明天的技術(shù)更新。同時(shí),來(lái)自客戶的需求變更也是我們必須面對(duì)的另一個(gè)壓力。一句經(jīng)典的話是這樣描述客戶的變更:“當(dāng)我看到時(shí)我的需求就變更了。(I changed just when I saw it.)”過(guò)去我們用需求說(shuō)明書(shū)來(lái)抑制用戶的變更,現(xiàn)在發(fā)現(xiàn)不能這樣了。敏捷軟件開(kāi)發(fā)提出了許多應(yīng)對(duì)用戶變更的辦法,其中建立低耦合高內(nèi)聚的軟件結(jié)構(gòu)也是辦法之一。系統(tǒng)中的所有對(duì)象都有自己的明確職責(zé),這個(gè)職責(zé)應(yīng)當(dāng)不多且高度相關(guān)。每個(gè)對(duì)象都應(yīng)當(dāng)只完成自己的職責(zé),而把其它的任務(wù)交給別人去做。正如我前面提到的例子,評(píng)審表對(duì)象只完成與評(píng)審表相關(guān)的操作,而在它需要完成的任務(wù)中,需要使用評(píng)審計(jì)劃數(shù)據(jù)的相關(guān)功能,交給評(píng)審計(jì)劃對(duì)象去完成,評(píng)審表只管調(diào)用。這樣的構(gòu)造要求開(kāi)發(fā)者相互協(xié)調(diào),彼此多交流,同時(shí),也需要有人來(lái)統(tǒng)一規(guī)劃,站在全局的設(shè)計(jì)這個(gè)系統(tǒng)。通過(guò)這些,我們才可以適應(yīng)變化,提高設(shè)計(jì)水平。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
