开发健壮的企业级应用的研究
?開發(fā)健壯的企業(yè)級(jí)應(yīng)用的研究
???? Research on Develop Hale Enterprise Applications
????????????????????????? 1.03版
????????????????????????? 作者:???? shendl_s@hotmail.com
?
[注:???這篇文章,是我的原創(chuàng)。我同時(shí)也在其他網(wǎng)站發(fā)布了這篇文章。不要誤會(huì)我掠人之美哦^-^???? ?]
????
?
?
?
?
?
?
?
????????????????????? 寫在前面的話
?這是2006年,我參加華中科技大學(xué)碩士論文答辯寫的一篇論文。但是,這篇論文寫的太過Blog化,被導(dǎo)師否決了。我打算重寫一篇學(xué)位論文,所以,這里把這篇過氣的論文公布出來。
?我是華中科技大學(xué)電子信息工程系電子信息工程碩士研究生。 大家可以給我寫E-Mail來聯(lián)系我。
??? 我主要使用的語(yǔ)言是Java。Java社區(qū)非常活躍,至少現(xiàn)在是如此,可以說是現(xiàn)在最先進(jìn)的一門編程語(yǔ)言。當(dāng)然,未來,一切還未注定!
??? 動(dòng)態(tài)面向?qū)ο笳Z(yǔ)言,比如Smalltalk,Ruby,Python等這些語(yǔ)言,也非常有魅力。我相信,動(dòng)態(tài)面向?qū)ο蟮臋C(jī)制,在未來應(yīng)該得到發(fā)展和應(yīng)用。Java1.3引入的動(dòng)態(tài)代理,已經(jīng)為我們展現(xiàn)了強(qiáng)大的威力。
?Smalltalk僅僅只有幾個(gè)關(guān)鍵詞,就構(gòu)成了一門強(qiáng)大的面向?qū)ο笳Z(yǔ)言。動(dòng)態(tài)面向?qū)ο笳Z(yǔ)言,不需要類型聲明,如果實(shí)際類型一致,直接調(diào)用好了!當(dāng)然,也許這并不是靜態(tài)面向?qū)ο缶幊陶Z(yǔ)言的缺點(diǎn),畢竟,編譯器差錯(cuò)能夠幫助我們減少錯(cuò)誤。不過,如果能夠在編寫代碼時(shí),模擬運(yùn)行,也許也能夠在編寫代碼時(shí)為我們找出這些錯(cuò)誤。
?6月6日,我參加了Martin Fowler在上海交大的敏捷開發(fā)座談會(huì)。會(huì)上,Martin Fowler做了關(guān)于Ruby的演講。其中,將Ruby和DSL(特定領(lǐng)域語(yǔ)言)聯(lián)系到了一起。他認(rèn)為,Ruby簡(jiǎn)潔的語(yǔ)法,是DSL語(yǔ)言的理想表達(dá)工具。
?前些天,我看到Intellj的作者的一篇文章,也提出了發(fā)展DSL的設(shè)想,他正在致力于將Intellj制作成開發(fā)DSL的IDE工具。
?Martin Fowler認(rèn)為目前xml形式的DSL表達(dá)方式過于復(fù)雜,應(yīng)該使用普通英語(yǔ)直接表達(dá)。目前,我還是認(rèn)為XML格式的DSL比較好。我們可以自己用Java等語(yǔ)言編寫解釋程序,解釋自定義的xml形式的文本----DSL。XML格式的DSL的優(yōu)點(diǎn)是便于驗(yàn)證。
?總之,DSL目前尚未發(fā)展成熟,但是未來的前景還是非常樂觀的。不過,現(xiàn)在跟進(jìn),可能還太早。
??? 將來,AOP也會(huì)有很大的發(fā)展。AOP的混入機(jī)制,是AOP最強(qiáng)大的機(jī)制,未來如果有合適的場(chǎng)合,應(yīng)該會(huì)成為程序員手中又一個(gè)殺手锏。
??? 我想,未來可能會(huì)出現(xiàn)這樣一種語(yǔ)言:她集中了靜態(tài)面向?qū)ο缶幊陶Z(yǔ)言和動(dòng)態(tài)面向?qū)ο缶幊陶Z(yǔ)言的機(jī)制于一身,還直接支持AOP這樣的編程范式。
??? 盡管ASpectJ已經(jīng)擴(kuò)展了Java。但是,由于并不是標(biāo)準(zhǔn)的Java,所以,大家使用起來仍然有困難。當(dāng)然,現(xiàn)在Java不把AspcetJ引入Java標(biāo)準(zhǔn),可能也因?yàn)楝F(xiàn)在AOP還沒有成熟,眾多的產(chǎn)品和理念還沒有決出勝負(fù)。
??? 未來這樣的一種語(yǔ)言,也許是未來的Java,也可能是一種全新創(chuàng)造的語(yǔ)言,或者是一門動(dòng)態(tài)面向?qū)ο缶幊陶Z(yǔ)言的擴(kuò)展。不過,沒關(guān)系,還是Java的理念。到時(shí)候,Java社區(qū)的程序員也是可以毫不費(fèi)力的轉(zhuǎn)到新的社區(qū)的!
??? 在本文的最后,有一節(jié)是講“源代碼就是設(shè)計(jì)”。這里講到了,寫源代碼和寫文章很類似。不過,本人雖然博學(xué),但是對(duì)怎樣寫文章,這樣一門學(xué)問,確實(shí)沒什么研究。我希望,未來有這方面背景的程序員,能夠?qū)⑽恼聦W(xué)引入到程序員的世界來,這也是一件造福人類的事情!
??? 文學(xué)系的朋友們,你們論文的題目有了啊^-^
???????? 好了,廢話不說了,請(qǐng)看在下得拙著吧!
????????????????????????????????2006-06-23 于上海家中
??????????????? 目 錄
摘要…………………………………………………………………………………….I
ABSTRACT…………………………………………………………………………..II
1 什么是企業(yè)級(jí)應(yīng)用?4
2 為什么我們需要開發(fā)健壯的企業(yè)級(jí)應(yīng)用?6
3 什么是健壯的企業(yè)級(jí)應(yīng)用?7
3.1 什么是健壯的企業(yè)級(jí)應(yīng)用?7
3.2 企業(yè)級(jí)應(yīng)用的一般結(jié)構(gòu)?7
3.3 健壯的企業(yè)級(jí)應(yīng)用的一般結(jié)構(gòu)?8
4 怎樣開發(fā)健壯的企業(yè)級(jí)應(yīng)用?11
5 面向?qū)ο缶幊碳夹g(shù)?12
5.1 依賴于抽象,而不要依賴于具體實(shí)現(xiàn)?12
5.2 使用委派而不是繼承?13
5.3 “客戶—服務(wù)器”關(guān)系中,應(yīng)該是“瘦”客戶類,“胖”服務(wù)器類?15
5.4 類存在的意義是提供的服務(wù),而非保存的數(shù)據(jù)?15
5.5 單一功能的方法?17
5.6 單一職責(zé)的接口?18
5.7 用接口來隔離實(shí)現(xiàn)類?18
5.8 直接使用編程語(yǔ)言的概念進(jìn)行設(shè)計(jì)?19
5.9 盡量使用模式來解決問題?19
6 面向方面編程技術(shù)?20
6.1 AOP的重要概念?22
6.2 實(shí)現(xiàn)AOP的主要技術(shù)策略?25
6.3 Spring AOP框架?27
6.4 如何更好的使用AOP?28
7 面向關(guān)注軟件開發(fā)?29
8 敏捷開發(fā)方法的代表作—XP?30
8.1 XP的前提?30
8.2 為什么需要XP?31
9 融合XP的軟件開發(fā)過程?32
9.1 獲取需求?32
9.2 測(cè)試驅(qū)動(dòng)開發(fā)和驗(yàn)收測(cè)試級(jí)重構(gòu)?33
9.3 單元測(cè)試驅(qū)動(dòng)開發(fā)和單元測(cè)試級(jí)重構(gòu)?35
9.4 小結(jié)?36
10 使用Java開發(fā)企業(yè)級(jí)應(yīng)用的參考架構(gòu)?36
10.1 JavaEE?36
10.2 “經(jīng)典”的JavaEE架構(gòu)?36
10.3? Java開源軟件?38
10.4 不用EJB的簡(jiǎn)單Java EE架構(gòu)?38
10.5 使用“輕量級(jí)容器”的Java EE架構(gòu)?42
11 總結(jié)?45
11.1 “源代碼就是設(shè)計(jì)”?45
11.2 總結(jié)?45
??????????????? 致? 謝?46
?????????????? 參 考 文 獻(xiàn)?46
什么是企業(yè)級(jí)應(yīng)用
?企業(yè)級(jí)應(yīng)用(Enterprise Applications),顧名思義,就是企業(yè)經(jīng)營(yíng)中需要使用的應(yīng)用程序。又叫作企業(yè)信息系統(tǒng)(Enterprise Information System)或者管理信息系統(tǒng)(Management Information Systems)。其主要任務(wù)是最大限度的利用現(xiàn)代計(jì)算機(jī)及網(wǎng)絡(luò)通訊技術(shù)加強(qiáng)企業(yè)的信息管理,通過對(duì)企業(yè)擁有的人力、物力、財(cái)力、設(shè)備、技術(shù)等資源的調(diào)查了解,建立正確的數(shù)據(jù),加工處理并編制成各種信息資料及時(shí)提供給管理人員,以便進(jìn)行正確的決策,不斷提高企業(yè)的管理水平和經(jīng)濟(jì)效益。可以說,它的涵蓋面是非常廣的,很難給它下一個(gè)確切的定義,但是我可以羅列一些個(gè)人的理解。[1]
?先舉幾個(gè)例子。企業(yè)級(jí)應(yīng)用包括工資單、患者記錄、發(fā)貨跟蹤、成本分析、信譽(yù)評(píng)估、保險(xiǎn)、供應(yīng)鏈、記賬、客戶服務(wù)以及外幣交易等。企業(yè)級(jí)應(yīng)用不包括車輛加油、文字處理、電梯控制、化工廠控制器、電話交換機(jī)、操作系統(tǒng)、編譯器以及電子游戲等。
?企業(yè)級(jí)應(yīng)用一般都涉及到持久化數(shù)據(jù)。數(shù)據(jù)必須持久化是因?yàn)槌绦虻亩啻芜\(yùn)行都需要用到它們—實(shí)際上,有些數(shù)據(jù)必須持久化若干年。在此期間,操作這些數(shù)據(jù)的程序往往會(huì)有很多變化。這些數(shù)據(jù)的生命周期往往比最初生成它們的那些硬件、操作系統(tǒng)和編譯器還要長(zhǎng)。在此期間,數(shù)據(jù)本身的結(jié)構(gòu)一般也會(huì)被擴(kuò)展,使得它在不影響已有信息的基礎(chǔ)上,還能表示更多的新信息。即使是有根本性的變化發(fā)生,或公司安裝了一套全新的軟件,這些數(shù)據(jù)也必須被“遷移”到這些全新的應(yīng)用程序上。
?企業(yè)級(jí)應(yīng)用一般都涉及到大量數(shù)據(jù)—一個(gè)中等規(guī)模的系統(tǒng)往往都包含1GB以上的數(shù)據(jù),這些數(shù)據(jù)是以數(shù)百萬(wàn)條記錄的方式存在的。巨大的數(shù)據(jù)量導(dǎo)致數(shù)據(jù)的管理成為系統(tǒng)的主要工作和挑戰(zhàn)!早期的系統(tǒng)使用的是索引文件系統(tǒng),如IBM的VSAM和ISAM。現(xiàn)代的系統(tǒng)往往采用數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)有層次數(shù)據(jù)庫(kù)、網(wǎng)狀數(shù)據(jù)庫(kù)、關(guān)系數(shù)據(jù)庫(kù)和對(duì)象數(shù)據(jù)庫(kù),還有對(duì)象—關(guān)系數(shù)據(jù)庫(kù)。現(xiàn)在最成熟,應(yīng)用也最多的是關(guān)系數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)的設(shè)計(jì)和演化已使其本身成為新的技術(shù)領(lǐng)域。
?數(shù)據(jù)庫(kù)在企業(yè)級(jí)應(yīng)用中處于重要的地位。選擇性能優(yōu)良的數(shù)據(jù)庫(kù)和有效的使用數(shù)據(jù)庫(kù),是開發(fā)企業(yè)級(jí)應(yīng)用的一項(xiàng)核心工作!
?最近興起的使用XML文件存儲(chǔ)少量數(shù)據(jù)這一技術(shù)。實(shí)際上,XML文件格式,就是早期的層次數(shù)據(jù)庫(kù)。它具有豐富的表達(dá)能力和簡(jiǎn)單的優(yōu)點(diǎn),但是數(shù)據(jù)庫(kù)發(fā)展的歷史已經(jīng)表明,XML文件不可能取代現(xiàn)在的關(guān)系數(shù)據(jù)庫(kù)和對(duì)象—關(guān)系數(shù)據(jù)庫(kù)。
?企業(yè)級(jí)應(yīng)用一般還同時(shí)涉及到很多人同時(shí)訪問數(shù)據(jù)。對(duì)于很多系統(tǒng)來說,人數(shù)可能在100人以下,但是對(duì)于一些基于Web的系統(tǒng),人數(shù)則會(huì)呈指數(shù)級(jí)增長(zhǎng)。要確保這些人都能夠正確地訪問數(shù)據(jù),就一定會(huì)存在這樣或那樣的問題。即使人數(shù)沒有這么多,要確保兩個(gè)人在同時(shí)操作同一數(shù)據(jù)項(xiàng)時(shí)不出現(xiàn)錯(cuò)誤,也是存在問題的。事務(wù)管理工具可以處理這個(gè)問題,但是最為程序員,我們?nèi)匀灰_地使用它們,這同樣不容易做到。
?企業(yè)級(jí)應(yīng)用還涉及到大量操作數(shù)據(jù)的用戶界面。有幾百個(gè)用戶界面是不足為奇的。用戶使用頻率的差異很大,他們也經(jīng)常沒什么技術(shù)背景。因此,為了不同的使用目的,數(shù)據(jù)需要很多種表現(xiàn)形式。
?企業(yè)級(jí)應(yīng)用也很少單獨(dú)存在,通常需要與企業(yè)的合作伙伴的企業(yè)級(jí)應(yīng)用集成。這些各式各樣的系統(tǒng)是在不同時(shí)期,采用不同的技術(shù)開發(fā)的。甚至連通訊機(jī)制都不相同:基于Socket的系統(tǒng)、CORBA系統(tǒng)、COM系統(tǒng)、Java的RMI、EJB和消息系統(tǒng),以及最新的Web Service等。企業(yè)經(jīng)常希望能夠用一種同一的通信技術(shù)來集成所有的系統(tǒng)。然而,每次這樣的集成工作都很難真正實(shí)現(xiàn),留下來的就是一個(gè)個(gè)風(fēng)格各異的集成環(huán)境。
?即使是某個(gè)企業(yè)統(tǒng)一了集成技術(shù),他們還是會(huì)遇到業(yè)務(wù)過程中的差異,以及數(shù)據(jù)庫(kù)中數(shù)據(jù)概念的不一致性。不同部門、不同系統(tǒng),甚至不同時(shí)期,都會(huì)對(duì)業(yè)務(wù)數(shù)據(jù)有不同的定義和理解。
?隨著企業(yè)級(jí)應(yīng)用的發(fā)展和集成,整個(gè)企業(yè)的企業(yè)級(jí)應(yīng)用程序就成了一個(gè)不同技術(shù)、不同數(shù)據(jù)混雜在一起組成的復(fù)雜系統(tǒng)。不斷的修改和添加新功能,也使系統(tǒng)的Bug越來越多,修改和添加新功能變得越來越困難。
?對(duì)于一些人來說,“企業(yè)級(jí)應(yīng)用”這個(gè)詞指的是大型系統(tǒng)。但是要注意,并不是所有的企業(yè)級(jí)應(yīng)用都是大型的,盡管它們可能都在為企業(yè)提供巨大的價(jià)值。
為什么我們需要開發(fā)健壯的企業(yè)級(jí)應(yīng)用
?開發(fā)企業(yè)級(jí)應(yīng)用程序并不是一件簡(jiǎn)單的事情,開發(fā)出一個(gè)能令客戶滿意的企業(yè)級(jí)應(yīng)用程序更非易事。這需要考慮太多的事情、克服太多的難關(guān)—技術(shù)上、商業(yè)上、人際關(guān)系上的。
?事實(shí)上,企業(yè)級(jí)應(yīng)用的主要成本和困難并不在開發(fā)軟件這個(gè)時(shí)期,而是在軟件的維護(hù)階段。企業(yè)級(jí)應(yīng)用在其交付使用后,其維護(hù)階段在軟件生命周期或生存期中占較大比重,有的可達(dá)軟件生命周期總成本(TCO)的50-70%。因?yàn)?#xff0c;企業(yè)的業(yè)務(wù)環(huán)境一直處于不斷的變化之中,這就要求企業(yè)級(jí)應(yīng)用也要能夠適應(yīng)企業(yè)的變化。而這些可能的變化,對(duì)于開發(fā)者來說,是很難預(yù)見的。如果開發(fā)的企業(yè)級(jí)應(yīng)用,其架構(gòu)沒有很強(qiáng)的適應(yīng)能力、不夠健壯的話,那么在維護(hù)階段修改軟件,或者增加新功能將是極其困難的,甚至是不能做到,必須推倒重來!
?業(yè)務(wù)需求的變化,才是企業(yè)級(jí)應(yīng)用最大的風(fēng)險(xiǎn)和難點(diǎn)!而且,這種變化,基本上每個(gè)項(xiàng)目都會(huì)出現(xiàn),這是企業(yè)級(jí)應(yīng)用開發(fā)的“常態(tài)”。甚至,一般在軟件開發(fā)的過程中,就會(huì)出現(xiàn)業(yè)務(wù)需求的變化。
?現(xiàn)在,市面上有很多套裝的企業(yè)級(jí)應(yīng)用,如ERP,CRM,財(cái)務(wù)軟件等。很多企業(yè)花了大價(jià)錢買了回來,但是應(yīng)用下來,失敗的案例很多。不僅造成了軟件購(gòu)置費(fèi)用的浪費(fèi),更嚴(yán)重的是擾亂了企業(yè)正常的業(yè)務(wù)活動(dòng),造成了嚴(yán)重的損失。也使不少企業(yè)對(duì)信息化望而卻步。
?企業(yè),作為市場(chǎng)經(jīng)濟(jì)的主體,其面臨的內(nèi)外部環(huán)境是在不斷變化的,企業(yè)本身也會(huì)針對(duì)這種變化,經(jīng)常性的調(diào)整其組織結(jié)構(gòu)和業(yè)務(wù)流程。并不存在適用于所有企業(yè)的一套一成不變的組織結(jié)構(gòu)和處理流程。
?使用套裝軟件,就好比是“削足適履”,為了適應(yīng)軟件的需要,改變企業(yè)原來運(yùn)轉(zhuǎn)正常的組織結(jié)構(gòu)和業(yè)務(wù)流程,實(shí)在是不智之舉。企業(yè)級(jí)應(yīng)用程序,是為企業(yè)服務(wù)的,應(yīng)當(dāng)服從企業(yè)的需要,而不是相反,企業(yè)成了軟件的奴隸!
?IBM的廣告詞“隨需應(yīng)變的軟件”,就是企業(yè)級(jí)應(yīng)用軟件業(yè)者理想中的軟件。可是,依靠IBM,就能夠開發(fā)出“隨需應(yīng)變的軟件”嗎?當(dāng)然不可能,一切只能夠靠我們程序員自己才能做到!
什么是健壯的企業(yè)級(jí)應(yīng)用
什么是健壯的企業(yè)級(jí)應(yīng)用
?“隨需應(yīng)變的軟件”,就好像是塑膠泥,我們可以任意拿捏,變化出不同的形狀。“隨需應(yīng)變的軟件”,必然是健壯的軟件,不論怎樣折騰,都能夠應(yīng)對(duì)自如。
?什么是“健壯的企業(yè)級(jí)應(yīng)用”,對(duì)此我無法給出一個(gè)精確的定義,我只能夠羅列一些我的理解。
?“健壯的企業(yè)級(jí)應(yīng)用”,其各個(gè)部分應(yīng)該是低耦合、高內(nèi)聚的。其內(nèi)部的各個(gè)模塊之間的關(guān)系最低,且可以互相替換,從而可以方便地拆卸、替換和修改各個(gè)模塊。
?其核心思想,就是“接口”。各個(gè)部分之間通過接口,并且只通過接口互相銜接,一起合作。只要接口相同,那么這些模塊就可以互相替換。對(duì)于其他模塊來說,其合作部分的具體實(shí)現(xiàn)是不重要的。
?這樣,我們?cè)谛枰半S需應(yīng)變”的改變軟件時(shí),只要簡(jiǎn)單的提供在原有系統(tǒng)上接插上不同的實(shí)現(xiàn)模塊即可。
?這實(shí)際上,就是面向?qū)ο?#xff08;OO)思想的一種體現(xiàn)。更深入的說,就是“抽象”的思想。把具體的源代碼通過接口屏蔽、抽象起來。這樣,只要接口不變,那么無論源代碼怎樣變化,都不會(huì)影響整個(gè)軟件的正常運(yùn)行。
?當(dāng)然,不僅企業(yè)級(jí)應(yīng)用需要“健壯”,任何軟件都應(yīng)該是健壯的。但是,在企業(yè)級(jí)應(yīng)用程序的開發(fā)和維護(hù)過程中,由于其需求的多變性,就更需要是“健壯”的。
企業(yè)級(jí)應(yīng)用的一般結(jié)構(gòu)
?現(xiàn)在,讓我們?cè)賮砜匆豢雌髽I(yè)級(jí)應(yīng)用的結(jié)構(gòu)。企業(yè)級(jí)應(yīng)用在結(jié)構(gòu)上,一般可以分為三大模塊:表現(xiàn)模塊,業(yè)務(wù)模塊,領(lǐng)域模塊。這里,我將它們稱作“模塊”,而不是“層”。很多人喜歡劃分“層次”,但我覺得劃分“模塊”更合適。因?yàn)?#xff0c;“層”有上下之分,只能是上層調(diào)用下層;而“模塊”就沒有上下之分,可以根據(jù)實(shí)際情況任意調(diào)用。這里,我不想分清什么是上層,什么是下層,用模塊來表示應(yīng)該更加合適。
?一、表現(xiàn)模塊
?表現(xiàn)模塊,又叫作客戶端。用于向客戶提供使用軟件系統(tǒng)的途徑。一般有圖形用戶界面GUI,命令行界面,文本文件等等。這個(gè)模塊,僅僅是用來接收用戶請(qǐng)求,再將這個(gè)請(qǐng)求委派給業(yè)務(wù)模塊提供的方法(這就是業(yè)務(wù)模塊提供的服務(wù)),從而實(shí)現(xiàn)軟件的功能。一個(gè)軟件的好壞與否,與之無直接的關(guān)系。
?二、業(yè)務(wù)模塊
?業(yè)務(wù)模塊,封裝了為特定的業(yè)務(wù)需求提供服務(wù)的方法。表現(xiàn)模塊就是通過它提供的方法來實(shí)現(xiàn)業(yè)務(wù)需求的。所以,業(yè)務(wù)模塊是直接對(duì)應(yīng)于系統(tǒng)的業(yè)務(wù)需求的,是系統(tǒng)的關(guān)鍵和最重要的部分。
?三、領(lǐng)域模塊
?也許你會(huì)問:既然業(yè)務(wù)模塊已經(jīng)提供了客戶所需的功能,那么還要這個(gè)領(lǐng)域模塊干什么呢?其實(shí),這個(gè)領(lǐng)域模塊就是為業(yè)務(wù)模塊服務(wù)的更底層的功能。
?在整個(gè)軟件系統(tǒng)中,存在一些實(shí)體。這些實(shí)體包含了一些數(shù)據(jù)和責(zé)任,它們的交互協(xié)作,就可以實(shí)現(xiàn)軟件系統(tǒng)的業(yè)務(wù)功能。
?實(shí)現(xiàn)一個(gè)個(gè)業(yè)務(wù)需求的業(yè)務(wù)模塊,可能需要這些實(shí)體中的一個(gè)或者多個(gè)的功能和數(shù)據(jù)。
?這些實(shí)體的集合,就是領(lǐng)域模塊。由此可見,領(lǐng)域模塊實(shí)際上才是整個(gè)系統(tǒng)的核心和靈魂。業(yè)務(wù)模塊也只有委托它們才能提供系統(tǒng)所需的業(yè)務(wù)功能。
健壯的企業(yè)級(jí)應(yīng)用的一般結(jié)構(gòu)
?健壯的企業(yè)級(jí)應(yīng)用,在結(jié)構(gòu)上又應(yīng)該是怎樣的呢?
?一、表現(xiàn)模塊
?表現(xiàn)模塊僅僅是一個(gè)界面,用于向用戶提供使用系統(tǒng)的途徑而已。所以,盡可能“薄”的表現(xiàn)模塊,就是理想的表現(xiàn)模塊。“薄”的表現(xiàn)模塊,就可以在用戶想要改變用戶界面時(shí),輕松的加以改變。改變用戶界面,是系統(tǒng)變化最多的需求。
?使用MVC模式設(shè)計(jì)的表現(xiàn)模塊,可以分為3個(gè)組成部分:M(Model)模型,V(View)視圖,C(Control)控制器。
?其中,模型,是一些類,它們封裝了視圖所要呈現(xiàn)給用戶的數(shù)據(jù),也用來將用戶操作信息傳遞給后臺(tái)的控制器模塊。
?視圖,就是用戶界面,是用戶看到的那部分。它能夠接受用戶的請(qǐng)求,并將請(qǐng)求信息發(fā)送給控制器,再在控制器完成操作之后,從模型類中獲得數(shù)據(jù),展現(xiàn)給用戶。
?控制器,它接收到用戶請(qǐng)求之后,就委托業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)對(duì)象提供的服務(wù),完成業(yè)務(wù)功能。如果有數(shù)據(jù)需要返回給用戶,就將數(shù)據(jù)存放到Model模型類中,以備視圖取用。控制器,雖然是表現(xiàn)層的一部分。但是,它實(shí)際上是“業(yè)務(wù)代表模式”的一種應(yīng)用。所謂業(yè)務(wù)代表,是指在客戶端和業(yè)務(wù)服務(wù)層之間,增設(shè)一個(gè)“代表層”,所有客戶端到服務(wù)器的調(diào)用,都“委托”該層完成。[9]業(yè)務(wù)代表雖然身處表現(xiàn)模塊內(nèi),但實(shí)際上執(zhí)行的是調(diào)用業(yè)務(wù)服務(wù)模塊功能的任務(wù),完成的是業(yè)務(wù)功能。因此,不少人都將業(yè)務(wù)代表劃分在業(yè)務(wù)模塊內(nèi)。我也認(rèn)同這種劃分方法。至少,我們可以認(rèn)為,它是橫跨表現(xiàn)模塊和業(yè)務(wù)模塊的一個(gè)部分。
?表現(xiàn)模塊中,模型和視圖兩部分通常都很小,而且它們是表現(xiàn)模塊固有的,不能夠省略,就算夠“厚”(比如,富客戶端技術(shù)),也沒辦法變小。
?實(shí)際上,我們說企業(yè)級(jí)應(yīng)用的表現(xiàn)模塊太“厚”,都是指太“厚”的控制器。理想的控制器,只應(yīng)該根據(jù)接收到的界面上不同的請(qǐng)求,調(diào)用業(yè)務(wù)模塊的不同業(yè)務(wù)服務(wù),完成這些請(qǐng)求,然后將得到的數(shù)據(jù)塞進(jìn)Model模型類即可。
?我們常常犯的錯(cuò)誤,就是在控制器中塞進(jìn)了太多的應(yīng)該放在業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)類中的代碼。實(shí)際上,判斷控制器是不是太厚,有一個(gè)非常簡(jiǎn)單的方法:假設(shè)我們使用另一種表現(xiàn)模塊技術(shù),那么,這個(gè)新的表現(xiàn)模塊中的控制器類中有多少代碼和現(xiàn)有的控制器是重復(fù)的。如果存在重復(fù)代碼,就使用“重構(gòu)”技術(shù),先將它們提煉成方法,然后再移到業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)類中。這樣,我們就能夠得到一個(gè)理想的“瘦”表現(xiàn)模塊!
?二、業(yè)務(wù)模塊
?業(yè)務(wù)模塊,包括2個(gè)部分:一個(gè)是表現(xiàn)模塊的控制器,它是“業(yè)務(wù)代理”,提供的也是與業(yè)務(wù)相關(guān)的服務(wù)。我認(rèn)為,把它劃分在業(yè)務(wù)模塊也許比表現(xiàn)模塊更加合適。另一個(gè)是業(yè)務(wù)服務(wù)模塊,我用Service表示它,它封裝了為特定的業(yè)務(wù)需求提供服務(wù)的方法。它與控制器配合,共同完成用戶需要的業(yè)務(wù)功能。
?既然理想中的控制器是“瘦”的,而且所有的重復(fù)代碼都移到了業(yè)務(wù)服務(wù)模塊中。那么,理想的業(yè)務(wù)服務(wù)模塊必然是“胖”的。
?實(shí)際上,控制器和業(yè)務(wù)服務(wù)模塊,是典型的“客戶—服務(wù)器模式”。控制器作為客戶,需要調(diào)用作為服務(wù)器的業(yè)務(wù)服務(wù)模塊提供的服務(wù),來完成用戶需要的功能。所以,服務(wù)器越胖,提供的服務(wù)越多,那么系統(tǒng)的重復(fù)代碼就越少,功能也越強(qiáng)大!
?三、領(lǐng)域模塊
?業(yè)務(wù)服務(wù)模塊和領(lǐng)域模塊,實(shí)際上也是典型的“客戶—服務(wù)器模式”。業(yè)務(wù)服務(wù)模塊雖然提供的服務(wù)功能強(qiáng)大,很“胖”。但是,它的“胖”也是有限度的!它的“胖”來自于控制器中理應(yīng)屬于它管轄的重復(fù)代碼。實(shí)際上,在“控制器模塊—業(yè)務(wù)服務(wù)模塊”這對(duì)“客戶—服務(wù)器”關(guān)系中,是控制器模塊“瘦”,而業(yè)務(wù)服務(wù)模塊“胖”。
?而在“業(yè)務(wù)服務(wù)模塊—領(lǐng)域模塊”這對(duì)“客戶—服務(wù)器”關(guān)系中,則是作為“客戶”的業(yè)務(wù)服務(wù)模塊“瘦”,而作為“服務(wù)器”的領(lǐng)域模塊“胖”。
?領(lǐng)域模塊,主要就是領(lǐng)域模型(也叫作業(yè)務(wù)對(duì)象Bussiness Object)。領(lǐng)域模型,封裝了業(yè)務(wù)實(shí)體的數(shù)據(jù),還提供一些處理這些數(shù)據(jù)的服務(wù)(“服務(wù)”在編程語(yǔ)言中就是由方法提供的)。一般,在企業(yè)級(jí)應(yīng)用中,有一些領(lǐng)域模型需要持久化存儲(chǔ),就是保存到數(shù)據(jù)庫(kù)(關(guān)系型數(shù)據(jù)庫(kù)或?qū)ο髷?shù)據(jù)庫(kù))、文本文件、XML文件、序列化文件等持久地存儲(chǔ)起來,已備下次再用。這時(shí),需要持久化的業(yè)務(wù)對(duì)象就需要對(duì)應(yīng)的提供數(shù)據(jù)訪問服務(wù)的類(也叫作DAO,Data Access Object數(shù)據(jù)訪問對(duì)象)。
?這樣,一般的企業(yè)級(jí)應(yīng)用的領(lǐng)域模塊,主要有兩個(gè)模塊:領(lǐng)域模型(Domain Model)和數(shù)據(jù)訪問服務(wù)模塊(DAO)。
?在“業(yè)務(wù)服務(wù)模塊—領(lǐng)域模塊”這對(duì)“客戶—服務(wù)器”關(guān)系中,
應(yīng)該把業(yè)務(wù)服務(wù)模塊中所有可以移到領(lǐng)域模塊的領(lǐng)域模型類和數(shù)據(jù)訪問服務(wù)類中的代碼都移到領(lǐng)域模型類和數(shù)據(jù)訪問服務(wù)類中去。
?因?yàn)?#xff0c;業(yè)務(wù)模塊的控制器和業(yè)務(wù)服務(wù)模塊,它們與領(lǐng)域模塊的領(lǐng)域模型和數(shù)據(jù)訪問服務(wù)模塊之間的關(guān)系是“多對(duì)多”的關(guān)系。一個(gè)業(yè)務(wù)模塊可以使用零個(gè)或者多個(gè)領(lǐng)域模塊;一個(gè)領(lǐng)域模塊也可以被零個(gè)或者多個(gè)業(yè)務(wù)模塊所調(diào)用!所以,領(lǐng)域模塊越“胖”,提供的服務(wù)越多,業(yè)務(wù)模塊就越少重復(fù)代碼,系統(tǒng)的功能就越強(qiáng)大!
?現(xiàn)在,很多程序員都接受了UML的用例驅(qū)動(dòng)開發(fā)的思想。誠(chéng)然,用例驅(qū)動(dòng)開發(fā)的思想確實(shí)很好,但是很多程序員都由此犯了一個(gè)毛病:他們常常按照用例為系統(tǒng)的源代碼分包(就是Java中的Package,.Net中的namespace),錯(cuò)誤的將領(lǐng)域模塊的領(lǐng)域模型、DAO數(shù)據(jù)訪問服務(wù)類和業(yè)務(wù)模塊的控制器、業(yè)務(wù)服務(wù)類劃分在一個(gè)包里。實(shí)際上,領(lǐng)域模塊和業(yè)務(wù)模塊完全不同,它們并不是從屬于某一個(gè)用例的,而是屬于整個(gè)系統(tǒng)的,可以被多個(gè)業(yè)務(wù)模塊共同使用的。
?如果把領(lǐng)域模塊放在首次用到它們的業(yè)務(wù)模塊之中。那么我們就很難在其他業(yè)務(wù)模塊調(diào)用它們時(shí)很好的使用它們。因?yàn)?#xff0c;其他業(yè)務(wù)模塊的使用,可能會(huì)要求領(lǐng)域模型類和DAO類增加新的字段和方法。而將它們放在另一個(gè)業(yè)務(wù)模塊所在的包里,我們就很難將這些新增的功能放到領(lǐng)域模型類和DAO類中,使它們更“胖”。而是會(huì)傾向于在控制器類和業(yè)務(wù)服務(wù)類中增加方法。這樣,就會(huì)導(dǎo)致各個(gè)業(yè)務(wù)模塊中出現(xiàn)重復(fù)代碼,引發(fā)邏輯重復(fù)。
?
怎樣開發(fā)健壯的企業(yè)級(jí)應(yīng)用
?企業(yè)級(jí)應(yīng)用程序,按照是否能夠“隨需應(yīng)變”來劃分,可以分為兩類:健壯的和脆弱的。
?畫一根數(shù)軸,我把它叫作“軟件健壯度”圖。“健壯”在正方向,“脆弱”在負(fù)方向。有很多技術(shù)原理和開發(fā)方法,可以讓我們的應(yīng)用程序更加健壯,而違反這些原理和方法,我們的應(yīng)用程序就會(huì)變得更加脆弱,修改和擴(kuò)充新功能也更加困難。
?要想開發(fā)出健壯的企業(yè)級(jí)應(yīng)用,首先需要的就是開發(fā)人員扎實(shí)的編程技能和對(duì)編程原理的清楚認(rèn)識(shí)和應(yīng)用。沒有高水平的開發(fā)人員,而奢談“健壯的企業(yè)級(jí)應(yīng)用”,是毫無意義的。
?
面向?qū)ο缶幊碳夹g(shù)
?面向?qū)ο缶幊碳夹g(shù)(OOP,object-oriented programming)是近幾十年來編程領(lǐng)域最偉大的成就。健壯的企業(yè)級(jí)應(yīng)用程序,甚至任何健壯的軟件,必須首先是一個(gè)很好的實(shí)踐了OO思想的軟件。脫離了OO,就不用奢談什么合格的軟件了!
?面向?qū)ο缶幊碳夹g(shù),早已經(jīng)從昔日的神壇上走了下來。今天,任何一門主流的編程語(yǔ)言都是支持面向?qū)ο缶幊痰摹,F(xiàn)在,幾乎所有的程序員都自稱已經(jīng)掌握了面向?qū)ο缶幊痰募夹g(shù)。但是,真正掌握OO的程序員卻遠(yuǎn)遠(yuǎn)沒有這么多。而且,面向?qū)ο缶幊碳夹g(shù)還在飛速發(fā)展的過程中,我們還遠(yuǎn)沒有發(fā)掘出它的全部?jī)?nèi)涵。
?并不是說,你使用了面向?qū)ο蟮木幊陶Z(yǔ)言開發(fā)軟件,你就能夠開發(fā)出實(shí)踐了OO思想的軟件。
?要想開發(fā)出健壯的企業(yè)級(jí)應(yīng)用,我們需要的是全面皈依OO!
?面向?qū)ο缶幊痰膸讉€(gè)原理[2]:
依賴于抽象,而不要依賴于具體實(shí)現(xiàn)
?具體來說,就是依賴于接口(Interface),而不要依賴于接口的具體實(shí)現(xiàn)類。或者是,依賴于父類(最好是抽象的),而不是具體實(shí)現(xiàn)的子類。又或者是,依賴于父接口,而不是子接口。總之,只使用提供了所需方法的最基本的類型。這樣,當(dāng)程序需要改變時(shí),我們就可以僅僅提供另一個(gè)“服務(wù)器”實(shí)現(xiàn)類或者實(shí)現(xiàn)子類,然后在“客戶類”新建實(shí)例的地方更換成這個(gè)新實(shí)現(xiàn)類即可,無須更換“客戶類”的調(diào)用代碼。在使用IOC(反轉(zhuǎn)控制)容器,如Spring框架時(shí),我們甚至可以不用改動(dòng)“客戶類”的任何代碼,而只需更改元數(shù)據(jù)(在Spring 框架中,是簡(jiǎn)單的Xml文件),將舊的實(shí)現(xiàn)類的類名換成新的即可。
?使用越是抽象的接口或者類,我們可以選用的實(shí)現(xiàn)類也就越多!
?我們知道,在Java語(yǔ)言中有這樣幾種作用域:Private私有,Protected保護(hù),Package包,public公共。按照“依賴于接口Interface”這一原則,我們又有了一個(gè)新的作用域:Publish已發(fā)布。接口Interface中聲明的方法就是已發(fā)布的。既然我們現(xiàn)在是通過接口來使用實(shí)現(xiàn)類的方法,那么就是說,即使實(shí)現(xiàn)類還有其他的Public可見方法,我們也不會(huì)調(diào)用它們。
?比如說,A類實(shí)現(xiàn)了B和C接口。調(diào)用A類的客戶代碼中,B b=new A();這樣調(diào)用,那么我們通過對(duì)象b就只能夠調(diào)用B接口發(fā)布(Publish)的代碼,這也就保證了不會(huì)因?yàn)槌绦騿T一時(shí)的疏忽而造成了代碼不必要的耦合。如果需要改變B接口的實(shí)現(xiàn),假設(shè)D類實(shí)現(xiàn)了B接口,只需要在客戶代碼中改成這樣:B b=new D();就可以了。
?當(dāng)然,凡事總有例外的情況。對(duì)于特別簡(jiǎn)單,而且不大會(huì)改變的類,我們也可以直接使用實(shí)現(xiàn)類,而不是接口。如:表現(xiàn)層MVC模式中的Model模型類,還有領(lǐng)域模型類,它們主要是提供數(shù)據(jù),只有很少的方法(set/get方法不算),而且不經(jīng)常變化,所以一般我們直接使用它們,而不使用接口。
使用委派而不是繼承
?要讓一個(gè)類實(shí)現(xiàn)一些功能,有三種方法:
?1,在類中直接寫上實(shí)現(xiàn)功能的代碼。
?2,使用繼承。讓類繼承另一個(gè)類,從而可以使用另一個(gè)類的所有公共的和受保護(hù)的方法。
?3,使用委派。在一個(gè)類中,通過定義對(duì)象類型的實(shí)例變量,可以調(diào)用那些對(duì)象的方法,來提供功能。
?另外還有一種特殊的使用委派的方式—“回調(diào)模式”。就是在類的方法中聲明對(duì)象類型的參數(shù),然后調(diào)用這個(gè)參數(shù)的方法來提供功能。在使用時(shí),客戶類需要提供對(duì)象的實(shí)例作為參數(shù)傳給這個(gè)類。
?這三種方法中,對(duì)于沒有現(xiàn)存代碼的特殊功能,我們可以使用在類中直接寫上實(shí)現(xiàn)功能的代碼這一方法來實(shí)現(xiàn)。而在已經(jīng)存在擁有可以使用的方法的類時(shí),我們可以使用繼承或者委派使用它們的方法。
?如果我們使用繼承這種方式來獲得功能,那么我們就會(huì)獲得可能我們并不需要的大量的父類字段和功能。這樣的冗余,就會(huì)造成邏輯上的混亂。而且,Java只能夠進(jìn)行單繼承,即,一個(gè)類只能夠繼承一個(gè)父類。這樣,一旦繼承了一個(gè)類,就剝奪了它繼承的能力。
?也許你會(huì)問,為什么Java要取消多繼承呢?因?yàn)?#xff0c;多繼承提供的好處遠(yuǎn)比它造成的問題更多。如果2個(gè)父類的字段、方法同名怎么辦?而且,在邏輯上也會(huì)造成極大的混亂。
?繼承能夠做到的事情,委派一樣都能夠做到。而且做得更好!
?我們可以把所需的任務(wù)委派給任意多個(gè)類(別忘了,應(yīng)該盡量使用接口Interface,最好用上IOC容器),然后在提供服務(wù)的方法中,使用委派對(duì)象的方法來實(shí)現(xiàn)。這樣,在邏輯上,我們能夠借助于這些類實(shí)現(xiàn)所需要的功能,而沒有實(shí)現(xiàn)增加不需要的字段和方法。
?所以,我們應(yīng)該牢記“使用委派而不是繼承”這一條原則。當(dāng)然,也還是有場(chǎng)合可以使用“繼承”的。
?1,一個(gè)類和另一個(gè)類是純粹的擴(kuò)展關(guān)系,邏輯上沒有半點(diǎn)不符合的地方。如,幾何類和方形類。當(dāng)然,這種情況下,使用委派仍然是可以的,而且更加保險(xiǎn)。因?yàn)?#xff0c;常常有很多看上去非常像父子關(guān)系的類,實(shí)際上并不是真正的父子關(guān)系。父類可能有幾個(gè)子類不可能有的方法,這會(huì)成為一顆定時(shí)炸彈,在我們需要給子類增加相關(guān)方法時(shí)引發(fā)問題。
?2,模板方法模式(Template Method)[3]適用的情況下。當(dāng)知道如何實(shí)現(xiàn)一個(gè)工作流程,但不知道所有單個(gè)步驟被如何實(shí)現(xiàn)時(shí),使用模板方法模式比較簡(jiǎn)單。[4]在父類(通常是抽象類)中提供一個(gè)工作流方法,然后再提供幾個(gè)工作流方法需要用到的方法的抽象方法原型。子類只需要覆蓋這幾個(gè)抽象方法,就能夠提供不同的實(shí)現(xiàn)效果。這種用法也叫作“多態(tài)”。
?但是,即使是這種情況,委派仍然能夠勝任,而且比繼承提供的解決方案更加靈活,只是稍微要復(fù)雜一些。這就是Strategy策略模式[3]。策略模式將不變的行為集中到一個(gè)接口中。用接口的實(shí)現(xiàn)類來實(shí)現(xiàn)具體的功能。
?“依賴于抽象,而不要依賴于具體實(shí)現(xiàn)”和“使用委派而不是繼承”這兩條原則,其思想都是相同的:不要為類提供不需要的能力。只要正巧夠用就行。與其他類通訊時(shí),也只使用正巧夠用的服務(wù)。只有這樣,才能夠保證所有符合接口的類能夠被自由的替換成其他實(shí)現(xiàn)類。
“客戶—服務(wù)器”關(guān)系中,應(yīng)該是“瘦”客戶類,“胖”服務(wù)器類
?在使用委派的類中。委派的類就是“客戶”類,被委派的類,就是“服務(wù)器”類(也有人把它叫作“助手類”)。在這樣一對(duì)關(guān)系中,應(yīng)該盡量給客戶類“減肥”,而給服務(wù)器類“增肥”。因?yàn)?#xff0c;“客戶—服務(wù)器”關(guān)系中,客戶和服務(wù)器類一般是“多對(duì)多”的關(guān)系。變胖的服務(wù)器類可以在未來給更多的客戶類提供更好的服務(wù),而原本會(huì)在客戶類這邊重復(fù)的代碼就都消失了。
?這個(gè)原理,就是處理“委派關(guān)系”中兩個(gè)類之間關(guān)系的一個(gè)原則。
類存在的意義是提供的服務(wù),而非保存的數(shù)據(jù)
?對(duì)于類,最重要的是什么,一向都有爭(zhēng)議:有的認(rèn)為,類存在的意義就是封裝的數(shù)據(jù),有的認(rèn)為,類存在的意義就是提供的服務(wù)(也就是方法)。我贊同后者的觀點(diǎn)。類存在的意義就是通過方法提供服務(wù)。而類保存的數(shù)據(jù)也需要通過set/get方法暴露出來。
?我們都知道這樣一個(gè)經(jīng)典的公式:數(shù)據(jù)+算法=程序。而類就是同時(shí)封裝了數(shù)據(jù)及其相關(guān)算法的模塊。或者說是算法及其使用的數(shù)據(jù)的模塊。
?方法,是很早就出現(xiàn)的一個(gè)概念,在面向過程編程時(shí)代就是一個(gè)核心的概念,是那時(shí)最重要的抽象機(jī)制,它的出現(xiàn),使我們第一次擁有了將具體實(shí)現(xiàn)代碼屏蔽了起來的能力。
?方法,提供了一個(gè)程序執(zhí)行的點(diǎn)。在面向方面編程(AOP)中,叫作“連接點(diǎn)(join point)”。我們可以在方法調(diào)用的前后進(jìn)行攔截,增加其他代碼。這是AOP面向方面的編程思想,在下面會(huì)詳細(xì)講解。另外,客戶代碼調(diào)用方法時(shí),只需要給出方法名和參數(shù),并不需要了解方法的實(shí)現(xiàn),這也就給了“客戶—服務(wù)器”之間解除耦合的一次機(jī)會(huì)。方法的內(nèi)部實(shí)現(xiàn)可以任意改變,只要不改變方法簽名即可。
?在OOP中,我們還可以使用這個(gè)接口的另一個(gè)實(shí)現(xiàn)類提供的另一個(gè)方法實(shí)現(xiàn)版本,來提供不同的服務(wù)。
?我們知道類是封裝了數(shù)據(jù)和方法的集合。其實(shí),從用例驅(qū)動(dòng)開發(fā)的角度來看,是類封裝了服務(wù),然后服務(wù)需要使用一些數(shù)據(jù),就把這些數(shù)據(jù)也封裝在了類中。
?我編寫類的方式,是“客戶—服務(wù)器”的方式。使用委派,也就是使用了“客戶—服務(wù)器”的方式來編程。首先,是用戶提供的用例(XP中叫作“用戶故事”)。但是用例太寬泛了,不足以支持編程工作的展開。于是,用戶再提供每個(gè)用例的具體的事件流。在XP中,是提供驗(yàn)收測(cè)試,驗(yàn)收測(cè)試中也包含了事件流。
?事件流,就是驅(qū)動(dòng)我們開發(fā)的第一個(gè)“客戶”。理解了事件流,我們就可以畫出UML的序列圖。序列圖描述了系統(tǒng)的業(yè)務(wù)模塊提供了哪些服務(wù),從而完成事件流。我們可以直接將序列圖的邏輯編寫成控制器類。控制器就是我們?cè)创a中的第一個(gè)“客戶”。它和業(yè)務(wù)服務(wù)Service類構(gòu)成了“客戶—服務(wù)器”關(guān)系,可能也會(huì)和領(lǐng)域模塊的領(lǐng)域模型類構(gòu)成“客戶—服務(wù)器”關(guān)系。
?在控制器類這個(gè)“客戶”中,我們已經(jīng)實(shí)現(xiàn)了整個(gè)事件流的功能。只不過,有不少要調(diào)用的服務(wù)(也就是方法,不管是哪一個(gè)類的方法,控制器自己的方法,屬于Service業(yè)務(wù)服務(wù)類的方法或是領(lǐng)域模型類的方法等)還沒有實(shí)現(xiàn)。
?將這些服務(wù)按照邏輯和是否會(huì)在客戶端造成重復(fù)為標(biāo)準(zhǔn),分配給各個(gè)模塊的各個(gè)類。按照“針對(duì)接口”編程的原則,將這些服務(wù)分發(fā)到各個(gè)接口中去,而不是實(shí)現(xiàn)類。
?現(xiàn)在,雖然程序還沒有開發(fā)完成,但我們已經(jīng)知道程序在邏輯上已經(jīng)完成了,或者說,已經(jīng)設(shè)計(jì)完成了。我們只剩下兩項(xiàng)簡(jiǎn)單的實(shí)現(xiàn)層面的工作要做:1,編寫服務(wù)的實(shí)現(xiàn)代碼;2,利用重構(gòu),將這些方法移到最適合的接口和實(shí)現(xiàn)類中去。
?“服務(wù)”是我們所需要的。我們用方法來實(shí)現(xiàn)服務(wù)。而方法又可能需要一些變量來保存狀態(tài),其中有些狀態(tài)需要使用實(shí)例變量來保存。僅此而已!
?重構(gòu),其實(shí)總的思路也是盡量消除變量,特別是實(shí)例變量;盡量提煉出方法,而不使用變量。因?yàn)?#xff0c;變量是“實(shí)現(xiàn)”級(jí)別的,是直接的源代碼,是死的,不允許變化。而方法是“設(shè)計(jì)”級(jí)別的,是活的,只要方法簽名不變,其內(nèi)部的實(shí)現(xiàn)代碼可以任意變化。而且,變量不是一個(gè)“程序執(zhí)行點(diǎn)”,不可以攔截,而方法就可以攔截,如AOP面向方面編程,或者代理模式,裝飾者模式等的攔截。
?按照方法和數(shù)據(jù)的比例,類可以分為三種類型:只有數(shù)據(jù)和get/set方法的啞容器類,既有數(shù)據(jù)又有實(shí)際方法的一般類,只有方法沒有實(shí)例變量的類。
?1,只有數(shù)據(jù)和get/set方法的啞容器類
?它們僅僅是數(shù)據(jù)的容器。Martin Fowler將它們稱為“嬰兒類” [5]。作為一個(gè)起點(diǎn),是可以的,但是它并不成熟。甚至,我們很難認(rèn)為它們是真正面向?qū)ο蟮摹5?#xff0c;現(xiàn)實(shí)中,還是有不少這樣的類存在。比如MVC模式的表現(xiàn)模塊中的Model模型。它的任務(wù)就是封裝將要呈現(xiàn)給客戶的數(shù)據(jù)。還有,領(lǐng)域模塊的領(lǐng)域模型類,它封裝了業(yè)務(wù)實(shí)體的數(shù)據(jù)。但是,我們都可以在它們內(nèi)部封裝一些使用這些數(shù)據(jù)的方法。
?總之,碰到這種只有數(shù)據(jù)和get/set方法的啞容器類,請(qǐng)?zhí)貏e留意,看是否能夠重構(gòu),讓它成為正常的類。
?2,只有方法沒有實(shí)例變量的類
?由于方法是編程世界的一等公民,所以這種類型的類是正常的,健康的,在現(xiàn)實(shí)世界還是非常普遍的。這些類,是提供了服務(wù),但是服務(wù)的實(shí)現(xiàn)代碼不需要保存實(shí)例變量的類。像表現(xiàn)模塊(也許說它屬于業(yè)務(wù)模塊更貼切)的控制器類,業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)Service類,領(lǐng)域模塊的DAO數(shù)據(jù)訪問服務(wù)類都是這一類型的類。
?這些類有一個(gè)優(yōu)點(diǎn),那就是它們不怕多線程的應(yīng)用。因?yàn)樗鼈儾恍枰4嫣囟ㄓ诰€程的數(shù)據(jù)。所以,我們可以用單例模式來使用它們。即,整個(gè)應(yīng)用程序中,只生成這些類的一個(gè)實(shí)例,用于為所有用戶的請(qǐng)求提供服務(wù)。JavaEE的Web容器中,Servlet就是單例的。Spring框架管理的類,也可以使用單例。
單一功能的方法
?在面向?qū)ο蟮拈_發(fā)方法中,接口、類、方法都需要對(duì)應(yīng)于單一的邏輯概念。貫徹這一原則,就可以使接口、類和方法的數(shù)量變多、塊頭變小、關(guān)系變簡(jiǎn)單、邏輯變清晰。重構(gòu)的一大目標(biāo),就是將一個(gè)大方法編成多個(gè)小的單一責(zé)任的方法。單一責(zé)任的方法,很多都是重構(gòu)的結(jié)果。
?OO的委派,實(shí)際上就是委派給其他類的方法來提供服務(wù)。優(yōu)秀的OO軟件,就是一層一層的方法委派其他類(作為“服務(wù)器”的類)的方法來提供功能。其代碼的特點(diǎn)就是一個(gè)方法內(nèi)部調(diào)用了幾個(gè)方法來實(shí)現(xiàn)功能,這些方法的名字就解釋了它們的功能。然后,這些被調(diào)用的方法內(nèi)部又像這樣調(diào)用了一些方法。循環(huán)不已,直到最底層的充當(dāng)“服務(wù)器”的類的方法中是調(diào)用API類庫(kù)的幾個(gè)方法而告終。
單一職責(zé)的接口
?一個(gè)設(shè)計(jì)上的邏輯概念,應(yīng)該有且僅有一個(gè)提供對(duì)應(yīng)邏輯的接口。這條原則就是類的內(nèi)聚性原則:一個(gè)模塊的組成元素之間的功能相關(guān)性。Robert C. Martin把內(nèi)聚性和引起一個(gè)模塊或者類改變的作用力聯(lián)系起來。[2]
?“就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因”。接口也是類。所以,我們可以說“就一個(gè)接口而言,應(yīng)該僅有一個(gè)引起它變化的原因”。實(shí)際上,一個(gè)實(shí)現(xiàn)類,是很難做到“僅有一個(gè)引起它變化的原因”的。而接口這個(gè)純?cè)O(shè)計(jì)層面的概念就不同了,它是可以真正做到“僅有一個(gè)引起它變化的原因”這一要求的。即使一個(gè)實(shí)現(xiàn)類能夠做到“僅有一個(gè)引起它變化的原因”,那也可能會(huì)造成實(shí)現(xiàn)類太小、太多的問題。而接口只包含方法簽名,并沒有實(shí)現(xiàn)代碼,所以,即使存在大量的接口也沒有問題。
?職責(zé),就是“變化的原因”。如果你能夠想到多于一個(gè)動(dòng)機(jī)去改變一個(gè)類或接口,那么這個(gè)類或接口就具有多于一個(gè)的職責(zé)。
?那么,為什么要把兩個(gè)職責(zé)分類到兩個(gè)單獨(dú)的接口中呢?因?yàn)槊恳粋€(gè)職責(zé)都是一個(gè)變化的軸線。當(dāng)業(yè)務(wù)需求發(fā)生變化時(shí),該變化就會(huì)反映為接口的職責(zé)的變化。如果一個(gè)接口承擔(dān)了多余一個(gè)的職責(zé),那么引起它變化的原因就會(huì)有多個(gè)。
?如果一個(gè)接口承擔(dān)的職責(zé)過多,就等于把這些職責(zé)都耦合到了一起。一個(gè)職責(zé)的變化,可能會(huì)引起接口完成其他職責(zé)的能力被削弱。
?比如說,一個(gè)業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)模塊有兩類業(yè)務(wù)服務(wù),一類是提供增刪改查領(lǐng)域模型數(shù)據(jù)的服務(wù),另一類是判斷數(shù)據(jù)完整性和正確性的服務(wù)。那么,這些服務(wù)就需要分別存放在兩個(gè)不同的業(yè)務(wù)服務(wù)Service接口中。這樣,在需求變化,從而導(dǎo)致服務(wù)改變時(shí),我們可以只改變一個(gè)接口,而另一個(gè)接口及其所有客戶類都不會(huì)受到影響。
用接口來隔離實(shí)現(xiàn)類
?一個(gè)設(shè)計(jì)上的邏輯概念,應(yīng)該有且僅有一個(gè)提供對(duì)應(yīng)邏輯的接口。現(xiàn)實(shí)中,我們的實(shí)現(xiàn)類可能并不是這么純正的,可能,我們的實(shí)現(xiàn)類同時(shí)實(shí)現(xiàn)了很多個(gè)接口,這樣的實(shí)現(xiàn)類,叫作“雜湊類”。但是這并沒有多大的關(guān)系,因?yàn)檫@僅僅是一個(gè)“實(shí)現(xiàn)”級(jí)別的問題。我們?nèi)匀粨碛幸粋€(gè)純正的接口,我們?cè)谑褂眠@個(gè)實(shí)現(xiàn)類的時(shí)候,是通過接口來使用的。這樣,雜湊類實(shí)現(xiàn)的其他接口的方法,我們并不會(huì)使用,也不能夠使用。接口的任何實(shí)現(xiàn)類都和“客戶”調(diào)用代碼無關(guān)!
直接使用編程語(yǔ)言的概念進(jìn)行設(shè)計(jì)
?軟件開發(fā)的真正進(jìn)步依賴于編程技術(shù)的進(jìn)步,而這又意味著編程語(yǔ)言的進(jìn)步。C++就是這樣的一個(gè)進(jìn)步。它已經(jīng)取得了爆炸式的流行,因?yàn)樗且婚T直接支持更好的軟件設(shè)計(jì)的主流編程語(yǔ)言。
?C++在正確的方向上邁出了一步,但是還需要更大的進(jìn)步。[6]
?Java就是這樣一門比C++更加先進(jìn),更加面向?qū)ο蟮恼Z(yǔ)言。Java可以更加有效的直接支持軟件設(shè)計(jì)。
?我們?cè)谶M(jìn)行軟件設(shè)計(jì)時(shí),應(yīng)該直接使用Java的概念來描述軟件系統(tǒng),進(jìn)行設(shè)計(jì)。這樣,我們就可以直接將設(shè)計(jì)轉(zhuǎn)化成實(shí)現(xiàn)。既然我們主張“針對(duì)接口編程”,那么,我們就應(yīng)該主要使用“接口(Interface)”這個(gè)概念來描述系統(tǒng)。另外,既然我們更重視“服務(wù)”,也就是方法,那么我們就使用方法,而不是數(shù)據(jù)來描述接口。
?XP提出的CRC圖(Class,Responsibilities,Collaboration類、責(zé)任、類間關(guān)系)很符合我們的要求[7]。CRC圖,描述了一個(gè)類,我們這里通常是描述一個(gè)接口Interface。其中的責(zé)任,就是服務(wù),也就是方法。它是接口的內(nèi)在要求,是接口之所以存在的原因。類間關(guān)系,就是接口和其它接口之間的關(guān)系。接口之間互相協(xié)作,才能夠完成業(yè)務(wù)功能。
?我們?cè)谶M(jìn)行軟件設(shè)計(jì)時(shí),不要考慮設(shè)計(jì)的具體實(shí)現(xiàn)的細(xì)節(jié)問題。我們只需要考慮接口應(yīng)該提供那些服務(wù),以及和哪些接口協(xié)作,怎樣協(xié)作即可。
盡量使用模式來解決問題
?內(nèi)行的設(shè)計(jì)者們都知道:不是解決任何問題都要從頭做起。它們更愿意復(fù)用以前的解決方案。當(dāng)找到一個(gè)好的解決方案,他們會(huì)一遍又一遍的使用它們。這些經(jīng)驗(yàn)是他們成為內(nèi)行的部分原因。因此,你會(huì)在很多面向?qū)ο笙到y(tǒng)中看到類和相互通信的對(duì)象的重復(fù)模式。這些模式解決特定的設(shè)計(jì)問題,使面向?qū)ο笤O(shè)計(jì)更靈活、優(yōu)雅,最終復(fù)用性更好。它們幫助設(shè)計(jì)者將新的設(shè)計(jì)建立在以往工作的基礎(chǔ)上。服用以往成功的設(shè)計(jì)方案。一個(gè)熟悉這些模式的設(shè)計(jì)者不需要再去發(fā)現(xiàn)它們,而能夠立即將它們應(yīng)用于設(shè)計(jì)問題中。[3]
?給“模式”所下的定義是這樣的:
?自從1994年,GoF的劃時(shí)代名著《設(shè)計(jì)模式—可復(fù)用面向?qū)ο筌浖幕A(chǔ)》問世之后,在程序員世界引起了軒然大波。眾多的模式著作紛紛推出,涉及各個(gè)領(lǐng)域。其中,Martin Fowler的《分析模式:可復(fù)用的對(duì)象模型》和《企業(yè)應(yīng)用架構(gòu)模式》,以及Sun Java中心編寫的《J2EE核心模式》都是其中的上乘之作。另外還有很多特定領(lǐng)域的模式著作。它們搜集了特定領(lǐng)域的一些模式。
?今天,簡(jiǎn)單的瀏覽這些模式書籍,我們就可以得到一大堆的專家級(jí)解決方案和經(jīng)驗(yàn)。
?實(shí)際上,使用模式來解決問題,并不需要你精研很多種模式,只需要你大致了解它們使用的場(chǎng)合,能夠在遇到這類問題時(shí)想起應(yīng)該到哪里尋找對(duì)應(yīng)的模式即可。模式,僅僅應(yīng)該作為一個(gè)字典。
?但是,《設(shè)計(jì)模式—可復(fù)用面向?qū)ο筌浖幕A(chǔ)》這本書還是應(yīng)該精心研讀。因?yàn)檫@本書中的23種模式,是最重要,應(yīng)用最廣泛的模式。
?當(dāng)然,凡事都不可太過,過猶不及!現(xiàn)在,有一些程序員凡事都以模式馬首是瞻,不分場(chǎng)合都要套用模式。我們應(yīng)該注意到,用模式來解決問題,一般情況下都會(huì)增加軟件的復(fù)雜性。本來,一個(gè)類解決的問題,現(xiàn)在需要幾個(gè)類協(xié)作才能夠解決。所以,對(duì)于明顯簡(jiǎn)單的功能,不宜使用復(fù)雜的模式。另外,一個(gè)軟件中不宜使用太多的模式。否則,會(huì)產(chǎn)生大量的類和類間的關(guān)系,使系統(tǒng)過于復(fù)雜。
面向方面編程技術(shù)
?面向方面編程AOP(Aspect-Oriented Programming)是1996年Gregor Kiczales在PARC提出的一種新的編程范式。AOP是一種與OOP截然不同的看待應(yīng)用程序結(jié)構(gòu)的方式,按照AOP的觀念,系統(tǒng)被分解為方面(aspect)而不是對(duì)象。
?OOP是一種成功的、極具表現(xiàn)力的編程范式,很多領(lǐng)域概念都可以自然的表達(dá)為對(duì)象,從而將其中通用的代碼模塊化。然而,還是有OOP照顧不到的角落。
?衡量OOP成功與否的標(biāo)準(zhǔn)就是它在多大程度上避免了代碼重復(fù)。
?代碼重復(fù)是最糟糕的代碼臭味。只要出現(xiàn)重復(fù)的代碼,必定有什么地方存在嚴(yán)重的問題—要么是設(shè)計(jì)有問題,要么是實(shí)現(xiàn)有問題。
?一般情況下,OOP能夠很好地避免代碼重復(fù)。具體繼承可以幫助我們?cè)诓煌愋椭g共享相同的行為;多態(tài)讓我們可以用同樣的方式處理不同類型的對(duì)象,將注意力集中到它們的共同之處。
?而委派,使類之間構(gòu)成了“客戶—服務(wù)器”關(guān)系。客戶類代碼簡(jiǎn)單的調(diào)用服務(wù)器類的方法即可完成眾多的功能“委派”。
?但是,有些時(shí)候,我們無法用OOP避免代碼重復(fù),或者無法用OOP得到一個(gè)優(yōu)雅的解決方案。
?就拿日志記錄來說吧。假設(shè)我們要對(duì)每一個(gè)控制器類的每一個(gè)方法調(diào)用都進(jìn)行日志記錄。那么,使用OOP委派的編程風(fēng)格,也就是使用“客戶—服務(wù)器”調(diào)用模式。我們需要在軟件的每一個(gè)控制器類的每一個(gè)方法中都增加一行調(diào)用Log日志記錄的代碼。盡管OOP委派已經(jīng)很好的將記錄日志的服務(wù)封裝在作為“服務(wù)器類”的Log類中,但是,作為“客戶—服務(wù)器”調(diào)用模式,總是不能夠去掉客戶類中調(diào)用服務(wù)器的服務(wù)的代碼。如果很多個(gè)“客戶類”都需要調(diào)用“服務(wù)器類”提供的“服務(wù)”,那么“客戶類”中的調(diào)用代碼,就會(huì)不可避免的存在大量的重復(fù)。這是“客戶—服務(wù)器”這種客戶主調(diào)模式不可避免的弊端。
?當(dāng)然,老實(shí)說,這類“客戶類”中的重復(fù)出現(xiàn)的調(diào)用代碼,并不是什么大不了的問題。存在這些重復(fù)代碼的軟件,照樣可以是一個(gè)比較健壯的軟件。這也正是我把OOP面向?qū)ο缶幊碳夹g(shù)作為開發(fā)有效軟件的第一技術(shù)的原因。
?但是,今天,AOP面向方面編程提供了另一種調(diào)用模式,使客戶類能夠不需要調(diào)用代碼,就能夠獲得所需的功能!這樣,就完全消除了重復(fù)代碼。
?AOP的調(diào)用模式,就是好萊塢原則:“不要試圖聯(lián)系我們,我們到時(shí)候自會(huì)通知你。”這完全不同于OOP委派的“客戶—服務(wù)器”調(diào)用模式。OOP中,如果一個(gè)類(客戶類)需要另一個(gè)類(服務(wù)器類)提供的服務(wù),就需要在客戶類的源代碼中顯式地加上調(diào)用代碼。而在AOP的“好萊塢原則”中,如果一個(gè)類(客戶類)需要另一個(gè)類(服務(wù)器類)提供的服務(wù),那么就不需要在客戶類中作任何需要服務(wù)的聲明。而是在服務(wù)器類中指定需要將服務(wù)提供給哪些客戶類。
?用我們現(xiàn)實(shí)生活中的事情做個(gè)類比:
?我需要坐出租車,那么我就需要親自打電話去給出租車公司,調(diào)用它們的“服務(wù)”。這就是OOP委派的“客戶—服務(wù)器”調(diào)用模式的工作原理。出租車公司提供專業(yè)化的出租車服務(wù),客戶只需要簡(jiǎn)單的調(diào)用它們提供的服務(wù)。
?或者,我走在路上,隨地扔了一張廢紙。然后,清潔工會(huì)將這張廢紙撿走。這里,我并沒有主動(dòng)要求清潔工提供服務(wù),而是清潔工自己為我提供的服務(wù)。
?又或者,我每天走在公路上。但是,這條公路并不是我建造的,它的維修也和我無關(guān)。
?清掃垃圾,維修道路、橋梁,實(shí)際上是政府為公民提供的一項(xiàng)服務(wù),或者叫作“基礎(chǔ)設(shè)施”,不管是有形的基礎(chǔ)設(shè)施,還是無形的基礎(chǔ)設(shè)施。
?政府提供“基礎(chǔ)設(shè)施”(如:清掃垃圾,維修道路等),和AOP的“好萊塢原則”原理是一致的。我把“好萊塢原則”稱作是“客戶—基礎(chǔ)設(shè)施”調(diào)用模式。“客戶”,就是實(shí)際上需要服務(wù)的類。“基礎(chǔ)設(shè)施”,就是為客戶封裝和提供服務(wù)的類(AspectJ這樣直接支持AOP的語(yǔ)言中,叫作“方面”,而在像SpringAOP這樣的OOP語(yǔ)言的AOP框架中,還是用一般的類來表示“基礎(chǔ)設(shè)施”)。“客戶—基礎(chǔ)設(shè)施”調(diào)用模式中,是“基礎(chǔ)設(shè)施”自己作用于“客戶”。“客戶”根本不知道有“基礎(chǔ)設(shè)施”的存在!
?基礎(chǔ)設(shè)施,又分為兩部分:封裝服務(wù)的類和指定該類為哪些客戶類的哪些方法服務(wù)的部分。
AOP的重要概念
?首先,讓我們來澄清AOP中的各個(gè)重要概念的定義。由于AOP興起時(shí)間不久,而且流派眾多,再加上國(guó)內(nèi)翻譯又各異,所以在這些概念的定義上有很多不同的版本。[8]
?一、關(guān)注(concern)
?一個(gè)關(guān)注可以使一個(gè)特定的問題、概念、或是應(yīng)用程序的興趣區(qū)間。或者說,是“涉眾”對(duì)應(yīng)用程序的期望。涉眾,就是與程序有關(guān)的人或者物,如客戶,程序員,關(guān)聯(lián)系統(tǒng),客戶端等應(yīng)用程序的環(huán)境中的某些部分。總而言之,是應(yīng)用程序必須達(dá)到的一個(gè)目標(biāo)。
?日志、安全性、事務(wù)管理、性能要求、用戶管理、角色管理、角色授權(quán)管理等等,都是系統(tǒng)中常見的“關(guān)注”。在一個(gè)OO的應(yīng)用程序中,關(guān)注可能已經(jīng)被代碼模塊化了,也可能還散落在整個(gè)對(duì)象模型之中。
?實(shí)際上,“關(guān)注”不是AOP獨(dú)有的概念,應(yīng)該是應(yīng)用程序都有的一個(gè)概念。它有些類似于面向?qū)ο蟮腢ML圖中“用例”,或者是XP的“故事”。
?二、橫切關(guān)注(crosscuting concern)
?如果一個(gè)關(guān)注的實(shí)現(xiàn)代碼散落在很多個(gè)類或方法之中(如:日志、安全性檢查),我們就稱之為“橫切關(guān)注”。
?如果用OOP實(shí)現(xiàn)橫切關(guān)注,那么必然會(huì)造成調(diào)用代碼重復(fù)。如果我們使用AOP實(shí)現(xiàn)橫切關(guān)注,就可以讓客戶類中不必進(jìn)行任何代碼調(diào)用。
?橫切關(guān)注,正是AOP的用武之地。
?三、方面(aspect)
?一個(gè)方面是對(duì)一個(gè)橫切關(guān)注的模塊化,它將那些本來散落在各處的、用于實(shí)現(xiàn)這個(gè)關(guān)注的代碼規(guī)整到一處。它一般包括兩個(gè)模塊:封裝服務(wù)代碼的模塊和指定該服務(wù)為哪些客戶類的哪些方法服務(wù)的 模塊。
?四、連接點(diǎn)(join point)
?程序執(zhí)行過程中的一點(diǎn)。如:
?1,方法調(diào)用(method invocation):對(duì)方法(可能包括構(gòu)造器)的調(diào)用,不過并非所有AOP框架都支持在對(duì)象構(gòu)造時(shí)的增強(qiáng)(advise)。
?這是最重要,最常用的連接點(diǎn)。我們?cè)诿嫦驅(qū)ο缶幊碳夹g(shù)中曾經(jīng)說過,重構(gòu)的一大手段和目標(biāo)就是構(gòu)造大量的方法。因?yàn)?#xff0c;方法是一個(gè)連接點(diǎn),是一個(gè)抽象,我們可以利用方法這一層抽象,任意的修改方法內(nèi)的實(shí)現(xiàn)代碼。所以,我們的代碼中應(yīng)該是大量存在方法這個(gè)蘭接點(diǎn)的。即使沒有,我們也可以在應(yīng)用AOP編程時(shí)重構(gòu),在需要攔截的地方重構(gòu)出一個(gè)方法,來作為連接點(diǎn)!
?有些AOP實(shí)現(xiàn),如JBoss AOP,就只提供了方法調(diào)用這一種連接點(diǎn)。
?2,字段訪問(field access)
?讀或者寫實(shí)例變量。同樣,并非所有的AOP框架都支持對(duì)字段訪問的增強(qiáng)。那些支持這類增強(qiáng)的AOP框架都可以區(qū)分讀操作和寫操作。
?Spring AOP,JBoss AOP都不支持字段攔截。字段攔截是一種潛在的危險(xiǎn),它違反了OO的封裝原則。
?一般來說,我認(rèn)為最好還是不要使用字段增強(qiáng)。OO程序中對(duì)字段的訪問,可以用set/get屬性這樣的方法調(diào)用來代替。AOP對(duì)字段的攔截,通常也可以通過方法層面的增強(qiáng)來代替,從而保持對(duì)象的封裝。
?3,異常拋出(throws)
?特定的異常被拋出。JBoss AOP框架只支持方法調(diào)用。但是,仍然可以通過編程獲得異常拋出。實(shí)際上,異常拋出這個(gè)連接點(diǎn),是方法調(diào)用這個(gè)連接點(diǎn)的衍生品。能夠攔截方法,那么一定能夠攔截拋出的異常。
?五、增強(qiáng)(advice)
?這個(gè)術(shù)語(yǔ)有很多種譯法,羅時(shí)飛在《精通Spring》一書中譯作:“裝備”;Spring中文論壇在《Spring Framework開發(fā)參考手冊(cè)》中譯作“通知”,石一楹在《expert one-to-one J2EE Development without EJB中文版》一書中譯作“增強(qiáng)”。這里,我就把它稱作是“增強(qiáng)”吧!
?增強(qiáng)(advice),是在特定的連接點(diǎn)執(zhí)行的動(dòng)作。很多AOP框架都以攔截器(interceptor)的形式來表現(xiàn)增強(qiáng)—所謂攔截器,就是這樣一個(gè)對(duì)象:當(dāng)連接點(diǎn)被調(diào)用時(shí),它會(huì)收到一個(gè)回調(diào)消息。增強(qiáng)的例子包括:
?1,在允許執(zhí)行連接點(diǎn)之前,檢查安全憑證。如Spring框架的一個(gè)附屬開源項(xiàng)目Acegi,就是這樣一個(gè)使用Spring AOP攔截方法訪問的項(xiàng)目。
?2,在執(zhí)行某個(gè)方法連接點(diǎn)之前開啟事務(wù),在連接點(diǎn)執(zhí)行完畢后提交或者回滾事務(wù)。Spring AOP框架提供了這個(gè)功能。
?六、切入點(diǎn)(pointcut)
?一組連接點(diǎn)的總稱,用于指定某個(gè)增強(qiáng)應(yīng)該在何時(shí)被調(diào)用。切入點(diǎn)常用正則表達(dá)式或別的通配符語(yǔ)法來描述。有些AOP實(shí)現(xiàn)技術(shù)還支持切入點(diǎn)的組合。
?切入點(diǎn)加上增強(qiáng),就是一個(gè)完整的方面,或者叫作“基礎(chǔ)設(shè)施”。可以實(shí)現(xiàn)橫切關(guān)注。
?七、引入(introduction)
?又譯作“混入”。指,為一個(gè)現(xiàn)有的類或接口添加方法或字段。這種技術(shù)用于實(shí)現(xiàn)Java眾多多重繼承,或者給現(xiàn)有的對(duì)象模型附加新的API。譬如說,可以通過引入讓一個(gè)現(xiàn)有的對(duì)象實(shí)現(xiàn)一個(gè)接口。
?八、混入繼承(mixin inheritance)
?一個(gè)“混入類”封裝了一組功能,這組功能可以被“混入”到現(xiàn)有的類當(dāng)中,并且無需求助于傳統(tǒng)的繼承手段。在AOP這里,混入是通過引入來實(shí)現(xiàn)的。在Java語(yǔ)言中,可以通過混入來實(shí)現(xiàn)多重繼承。
?九、織入(weaving)
?將方面整合到完整的執(zhí)行流程(或完整的類,此時(shí)被織入的就是引入)中。這是AOP的實(shí)現(xiàn)機(jī)制,AspectJ是使用預(yù)編譯器,在編譯之前通過生成代碼實(shí)現(xiàn)織入的,這叫作“靜態(tài)織入”。
?Spring AOP等AOP框架是在運(yùn)行時(shí)通過動(dòng)態(tài)代理生成匿名類的匿名對(duì)象的方式織入的。這叫作“動(dòng)態(tài)織入”。
?十、攔截器(interceptor)
?很多AOP框架(例如Spring和JBoss 4,但不包含AspectJ)用它來實(shí)現(xiàn)字段和方法的攔截(interceptor)。隨之而來的就是在連接點(diǎn)(譬如方法攔截)處掛接一條攔截器鏈(interceptor chain),鏈條上的每個(gè)攔截器通常會(huì)調(diào)用下一個(gè)攔截器。實(shí)際上,攔截是一種AOP的實(shí)現(xiàn)策略,而不是AOP的核心概念。
?十一、AOP代理(AOP proxy)
?AOP框架創(chuàng)建的對(duì)象,這個(gè)匿名類的匿名對(duì)象,它既委派目標(biāo)對(duì)象完成目標(biāo)對(duì)象的工作,也織入了攔截連接點(diǎn)的通知。在Spring AOP中,AOP代理可以是JDK動(dòng)態(tài)代理或者CGLIB代理。
?AOP代理也并不是所有AOP實(shí)現(xiàn)都有的一個(gè)概念,它是Spring AOP框架和JBoss AOP框架等動(dòng)態(tài)AOP框架愛實(shí)現(xiàn)AOP的根本方法。
實(shí)現(xiàn)AOP的主要技術(shù)策略
?AOP面向方面編程思想目前已經(jīng)有很多種實(shí)現(xiàn)和實(shí)現(xiàn)方法。下面是用于實(shí)現(xiàn)AOP的主要技術(shù)策略:
?一、J2SE動(dòng)態(tài)代理
?在Java中,實(shí)現(xiàn)AOP最顯而易見的策略莫過于使用Java1.3引入的動(dòng)態(tài)代理。動(dòng)態(tài)代理是一種強(qiáng)大的語(yǔ)言結(jié)構(gòu),它使我們可以為一個(gè)或多個(gè)接口“憑空”地創(chuàng)建實(shí)現(xiàn)對(duì)象,而不需要預(yù)先有一個(gè)實(shí)現(xiàn)類。
?如果需要用動(dòng)態(tài)代理實(shí)現(xiàn)環(huán)繞增強(qiáng),可以在其中調(diào)用必要的攔截器鏈。攔截器鏈上的最后一個(gè)攔截器將借助反射調(diào)用目標(biāo)對(duì)象—如果有目標(biāo)對(duì)象的話。
?動(dòng)態(tài)代理最大的好處在于:這是一種標(biāo)準(zhǔn)的Java語(yǔ)言特性。除了AOP框架之外不需要第三方庫(kù),也不會(huì)受到應(yīng)用服務(wù)器的任何影響。
?動(dòng)態(tài)代理的最大局限性在于:它只能針對(duì)接口進(jìn)行代理,不能針對(duì)類。即,需要“客戶—基礎(chǔ)設(shè)施”中的客戶類實(shí)現(xiàn)所需的接口,然后在程序中使用接口來使用新的對(duì)象的方法。不過,既然我們主張“針對(duì)接口編程”,那么這項(xiàng)限制并不是壞事,反而能夠使程序員養(yǎng)成良好的“針對(duì)接口編程”的習(xí)慣。
?另外,動(dòng)態(tài)代理只能對(duì)方法調(diào)用進(jìn)行增強(qiáng),而不能像AspectJ那樣對(duì)字段進(jìn)行增強(qiáng)。不過,既然我們使用方法來提供所有的服務(wù),那么“對(duì)字段進(jìn)行增強(qiáng)”這項(xiàng)功能也就是完全無用的,反而會(huì)引起程序員使用不良的編程方法。
?Spring框架,默認(rèn)時(shí)使用J2SE動(dòng)態(tài)代理提供AOP實(shí)現(xiàn)。開發(fā)者也可以指定使用“動(dòng)態(tài)字節(jié)碼生成”技術(shù)來實(shí)現(xiàn)AOP。Nanning框架也使用J2SE動(dòng)態(tài)代理提供AOP實(shí)現(xiàn)。
?二、動(dòng)態(tài)字節(jié)碼生成
?為了針對(duì)Java類提供代理,我們需要?jiǎng)討B(tài)代理之外的工具,那就是動(dòng)態(tài)字節(jié)碼生成(dynamic byte code generation)。在這方面,最流行的工具是CGLIB(Code Generation Library)。在Spring中,如果需要針對(duì)類(而不是接口)提供代理,就會(huì)用到CGLIB。它可以針對(duì)指定的類動(dòng)態(tài)生成一個(gè)子類,并覆蓋其中的方法,從而實(shí)現(xiàn)方法攔截。
?不過CGLIB有一個(gè)小問題:因?yàn)樗峭ㄟ^繼承來實(shí)現(xiàn)代理的,所以無法為final方法提供代理。
?三、Java代碼生成
?最笨的方法,就是讓容器生成新的源碼。這種方法,最早大概是MTS微軟事務(wù)服務(wù)器采用的,后來的Java的EJB也采用了這種方法來提供“基礎(chǔ)設(shè)施”。雖然這種方法很笨。但是,它們確實(shí)是早期的AOP嘗試。現(xiàn)在,隨著動(dòng)態(tài)代理和動(dòng)態(tài)字節(jié)碼生成技術(shù)的出現(xiàn),這種做法正在逐漸退出流行。
?另外,不得不補(bǔ)充一句:代碼生成,基本上都是最糟糕的編程技術(shù)。通常,我們都可以使用OOP的委派或者AOP來達(dá)到相同的目的。自動(dòng)生成代碼,將會(huì)在需要修改代碼時(shí)引起眾多可怕的問題!微軟是用了不少自動(dòng)代碼生成技術(shù),另外MDA模型驅(qū)動(dòng)開發(fā)方法和基于元數(shù)據(jù)的產(chǎn)生式編程都是常見的源代碼生成技術(shù)。對(duì)于這些技術(shù),我一直都抱著懷疑的態(tài)度!只要還有其他的方法可以實(shí)現(xiàn)目標(biāo),我是絕對(duì)不會(huì)使用源代碼生成這種技術(shù)的!
?四、使用定制的類加載器
?使用定制的類加載器,我們可以在一個(gè)類被加載時(shí)自動(dòng)對(duì)其進(jìn)行增強(qiáng)。即便用戶使用new操作符構(gòu)造實(shí)例,增強(qiáng)仍會(huì)生效。JBoss AOP和AspectWerk都采用這種做法對(duì)類進(jìn)行增強(qiáng),具體的增強(qiáng)信息則是在運(yùn)行時(shí)從XML配置文件中讀取。
?這種做法的風(fēng)險(xiǎn)在于:它偏離了Java的標(biāo)準(zhǔn)。在某些應(yīng)用服務(wù)器中,這種做法可能會(huì)導(dǎo)致問題,因?yàn)镴2EE服務(wù)器需要控制整個(gè)類加載的層級(jí)體系。
?五、語(yǔ)言擴(kuò)展
?如果我們希望把方面當(dāng)作一等公民來對(duì)待,就需要一種同時(shí)支持AOP和OOP的語(yǔ)言。為了達(dá)到這個(gè)目的,可以對(duì)現(xiàn)有的OO語(yǔ)言進(jìn)行擴(kuò)展,就像C++擴(kuò)展C語(yǔ)言、引入OO的概念那樣。最早出現(xiàn)的AOP實(shí)現(xiàn)AspectJ就對(duì)Java進(jìn)行了這樣的擴(kuò)展。
?AspectJ是功能最強(qiáng)大的AOP實(shí)現(xiàn)。但是,它是一種新的語(yǔ)言,語(yǔ)法比Java更復(fù)雜。而且,還需要使用AspectJ的預(yù)編譯器首先編譯AspectJ源碼,將它們變成增強(qiáng)后的Java代碼,然后再進(jìn)行Java的編譯,太過繁瑣。
Spring AOP框架
?在所有的AOP實(shí)現(xiàn)中,我認(rèn)為Spring AOP框架是最好的選擇。盡管它使用的動(dòng)態(tài)代理和動(dòng)態(tài)字節(jié)碼生成技術(shù)實(shí)現(xiàn)的AOP功能并不是最強(qiáng)大的,但是對(duì)于大多數(shù)情況已經(jīng)夠用,而且夠簡(jiǎn)單,沒有任何特殊的要求。
?并且,Spring框架很好的整合了AspectJ,在必要的時(shí)候,我們可以使用AspectJ的強(qiáng)大能力來實(shí)現(xiàn)AOP。
?Spring AOP是用純Java實(shí)現(xiàn)的,它不像AspectJ那樣需要特殊的編譯過程,也不需要像JBoss AOP那樣需要控制類裝載器層次,因此適用于J2EE容器或應(yīng)用服務(wù)器,也適用于任何使用Java的程序。不過,你的程序也必須要使用Spring框架來管理。Spring AOP只能夠?yàn)镾pring框架管理的Java類提供AOP服務(wù)。
?Spring目前只支持?jǐn)r截方法調(diào)用和異常拋出,不支持?jǐn)r截字段訪問。
?Spring提供代表切入點(diǎn)或各種通知類型的類。Spring使用術(shù)語(yǔ)advisor顧問來表示代表方面(aspect)的對(duì)象。它包含一個(gè)增強(qiáng)(advice)和一個(gè)指定特定連接點(diǎn)(join point)的切入點(diǎn)(pointcut)。
?Spring AOP框架的目標(biāo)并不是提供極其完善的AOP實(shí)現(xiàn)(雖然Spring AOP非常強(qiáng)大),而是提供一個(gè)和Spring IOC容器緊密結(jié)合的AOP實(shí)現(xiàn),幫助解決企業(yè)級(jí)應(yīng)用中的常見問題。
?因此,Spring AOP的功能通常是和Spring IOC容器聯(lián)合使用的。AOP通知是用普通的bean定義語(yǔ)法定義的。增強(qiáng)和切入點(diǎn)本身由Spring IOC管理—這是一個(gè)重要的和其他AOP實(shí)現(xiàn)的區(qū)別。
?有些事使用Spring AOP是無法容易或高效的實(shí)現(xiàn)的,如非常細(xì)粒度的對(duì)象,此時(shí)可以使用AspectJ。
?總的來說,Spring針對(duì)J2EE企業(yè)級(jí)應(yīng)用中大部分能用AOP解決的問題提供了一個(gè)優(yōu)秀的解決方案。
如何更好的使用AOP
?面向方面編程(AOP)為軟件開發(fā)提供了一種全新的視角—橫切的視角。讓我們看待軟件的視角從縱向的一維世界,變?yōu)槠矫娴氖澜纭R泊蟠筇岣吡宋覀冮_發(fā)軟件的能力。最近,AOP將會(huì)取代OOP的論調(diào)層出不窮。
?的確,AOP是一種不錯(cuò)的編程范式和思考方法,但是,OOP才是編程的根本。AOP只是OOP的有益補(bǔ)充,它們之間不是對(duì)立關(guān)系,而是互補(bǔ)的關(guān)系。AOP絕對(duì)不會(huì),也不可能替代OOP。兩者各有各的領(lǐng)地,而OOP的應(yīng)用范圍更大!
?如果一個(gè)軟件,連最起碼的OO原則都沒有遵循,又怎么可能奢望依靠AOP來達(dá)到健壯的目的呢!
?現(xiàn)在,在一些程序員中有一種傾向,就是在編程中一門心思的應(yīng)用AOP來編程。只要發(fā)現(xiàn)任何調(diào)用代碼重復(fù),都要使用方面來解決。
?這就造成了程序中使用了太多的方面。整個(gè)程序的邏輯變得難以理解和修改。
?我認(rèn)為,只應(yīng)該在出現(xiàn)大量調(diào)用代碼重復(fù)的情況下,才應(yīng)該使用AOP的“客戶—基礎(chǔ)設(shè)施”來解決,否則應(yīng)該使用OOP的“客戶—服務(wù)器”模式。對(duì)于難以決定應(yīng)該使用OOP還是AOP的場(chǎng)合,應(yīng)該使用OOP。
面向關(guān)注軟件開發(fā)
?面向?qū)ο缶幊碳夹g(shù),是一種解決業(yè)務(wù)關(guān)注的編程技術(shù)。比如說,一個(gè)企業(yè)級(jí)應(yīng)用,用戶需要用戶管理的功能,同時(shí)這個(gè)軟件的所有模塊都需要事務(wù)處理功能,日志功能和安全性檢查的功能。
?使用OO技術(shù)時(shí),當(dāng)我們要編寫實(shí)現(xiàn)用戶管理功能的代碼模塊時(shí),我們得到的系統(tǒng)關(guān)注(UML稱作“用例”,XP稱作“用戶故事”),是:用戶管理模塊,需要有增加、刪除、修改和查詢用戶的功能。同時(shí),所有的數(shù)據(jù)庫(kù)操作要有日志,要使用事務(wù),要檢查安全性,只有符合條件的用戶才能夠調(diào)用這些管理功能。
?然后,我們根據(jù)這個(gè)業(yè)務(wù)關(guān)注,編寫出滿足這些要求的軟件模塊。其中,日志、事務(wù)、安全性檢查代碼都與其它業(yè)務(wù)關(guān)注的實(shí)現(xiàn)模塊出現(xiàn)了代碼重復(fù)。
?可以說,OOP是用縱向的一維的視角來看待軟件系統(tǒng)的。
?AOP則是一種解決橫切公共關(guān)注[9]的編程技術(shù)。對(duì)于上面例子中的日志、事務(wù)、安全性檢查等公共的關(guān)注,我們不是把它們分散到各個(gè)業(yè)務(wù)關(guān)注中,而是集中在一起,構(gòu)成“橫切公共關(guān)注”,使用AOP的技術(shù),在一個(gè)模塊,也就是方面內(nèi)實(shí)現(xiàn),各個(gè)業(yè)務(wù)關(guān)注根本不用知道還存在這些關(guān)注,當(dāng)然,在實(shí)現(xiàn)業(yè)務(wù)關(guān)注的OO代碼中也不會(huì)體現(xiàn)這些橫切關(guān)注。
?我們可以看到,不論是OOP還是AOP,都只是一種編程的范式,是面向關(guān)注,解決關(guān)注的一種手段。
?但是軟件開發(fā)的根本是什么呢?就是“涉眾”的“關(guān)注”。軟件就是要滿足涉眾的關(guān)注,滿足了涉眾的關(guān)注,那么這個(gè)軟件就成功了!
?沒有“關(guān)注”,OOP和AOP就都成了無本之木,無源之水,沒有目標(biāo)的箭。而企業(yè)級(jí)應(yīng)用,最大的變數(shù)和風(fēng)險(xiǎn),就是客戶的需求的變化,也就是系統(tǒng)“關(guān)注”的變化。面向關(guān)注的軟件開發(fā)(COSD,concern-oriented Software Development),讓我們始終面向涉眾的“關(guān)注”這個(gè)根本的目標(biāo)來開發(fā)軟件。也讓我們?cè)凇瓣P(guān)注”的旗幟下,綜合運(yùn)用OOP和AOP編程技術(shù),分別解決縱向的業(yè)務(wù)關(guān)注和橫切的公共關(guān)注,相輔相成。
?面向關(guān)注軟件開發(fā)(COSD)的思想,是來自于AOP提出的概念“關(guān)注(concern)”,是重新審視OOP和AOP相互之間關(guān)系的思考結(jié)果。
?
敏捷開發(fā)方法的代表作—XP
?極限編程(Extreme Programming,簡(jiǎn)稱XP)是目前討論最多、實(shí)踐最多、爭(zhēng)議也是最多的一種敏捷開發(fā)方法。XP是一套能夠快速開發(fā)高質(zhì)量軟件所需的價(jià)值觀、原則和活動(dòng)的集合,使軟件能以盡可能快的速度開發(fā)出來并向客戶提供最高效益。
XP的前提
?極限編程的假設(shè):平滑成本曲線—變化的成本不會(huì)隨時(shí)間的推移而急劇上升。而非傳統(tǒng)中普遍認(rèn)為的“變化的成本隨時(shí)間的推移而以指數(shù)方式上升”[7]。傳統(tǒng)的計(jì)算機(jī)科學(xué)家認(rèn)為,對(duì)軟件的改變,會(huì)隨著項(xiàng)目時(shí)間的推移而變得越來越困難,其成本將是在項(xiàng)目開始初期就將這些需求考慮進(jìn)來的幾十倍。這在過去,確實(shí)是軟件開發(fā)的事實(shí)。
?但是,隨著面向?qū)ο缶幊碳夹g(shù)的發(fā)展,這一個(gè)假設(shè)已經(jīng)變得不符合實(shí)際情況了!具有良好面向?qū)ο缶幊碳夹g(shù)功底的程序員,完全可以開發(fā)出高內(nèi)聚、低耦合,結(jié)構(gòu)簡(jiǎn)單,易于修改的健壯的軟件,其“成本曲線”是“平滑的”,而非“陡峭的”。即使在軟件完成之后,對(duì)軟件進(jìn)行大規(guī)模的修改和添加新功能,也僅僅比在項(xiàng)目開始的時(shí)候明確定義這些需求增加極少的成本。
?所以,如果要使用XP取得成功,首先就需要能夠掌握本文前面章節(jié)介紹的那些方法,讓軟件的健壯度更高。可以說,XP對(duì)開發(fā)人員的要求是比較高的,需要有扎實(shí)的編程水平和面向?qū)ο蟮乃枷搿?br />為什么需要XP
?那么我們?yōu)槭裁床辉陧?xiàng)目開始之前就確定所有的需求,而要在項(xiàng)目進(jìn)行,甚至已經(jīng)完成的時(shí)候修改軟件呢?如果能夠做到這一點(diǎn),當(dāng)然好,但是,現(xiàn)實(shí)中,我們常常無法做到這一點(diǎn)。
?1,程序員對(duì)于開發(fā)軟件是專家,但卻不是軟件應(yīng)用領(lǐng)域的專家。而用戶雖然是軟件應(yīng)用領(lǐng)域的專家,但是他們往往不清楚軟件能夠做到什么,不能夠做到什么,或者做一項(xiàng)工作的成本如何。由于開發(fā)人員和用戶對(duì)相互領(lǐng)域的不了解,所以在軟件實(shí)際開發(fā)出來之前,開發(fā)人員和用戶都無法清楚的定義軟件需要的功能。
?往往直到軟件被實(shí)際開發(fā)出來的,用戶試用之后,才能夠明白自己到底需要怎樣的軟件。特別是在企業(yè)級(jí)應(yīng)用中,軟件開發(fā)完成之后,大規(guī)模的修改軟件是常事。
?2,隨著時(shí)間的推移,軟件應(yīng)用的環(huán)境本身就在發(fā)生變化。特別是在企業(yè)級(jí)應(yīng)用中,企業(yè)的業(yè)務(wù)環(huán)境一直處于不斷的變化之中,這就要求企業(yè)級(jí)應(yīng)用也要能夠適應(yīng)企業(yè)的變化。而這些可能的變化,對(duì)于開發(fā)者來說,是很難預(yù)見的。
?3,一般的軟件開發(fā)過程中,我們往往選擇分多個(gè)版本、階段開發(fā)軟件。就是首先開發(fā)一個(gè)初級(jí)的版本,發(fā)行。之后再在其基礎(chǔ)上添加其他功能。這是一般的軟件開發(fā)的策略。這也要求,我們不可能在軟件開發(fā)的初期就能夠考慮到未來若干年內(nèi)我們軟件的全部需求。比如,Windows操作系統(tǒng),這個(gè)軟件已經(jīng)歷時(shí)十多年,發(fā)布了無數(shù)個(gè)版本。顯然,最初開發(fā)Windows的時(shí)候是不可能預(yù)見到今天的Windows的。
?總之,軟件開發(fā)過程中,需求的變化是必然的,絕對(duì)的。不變的需求是暫時(shí)的,相對(duì)的。
?面對(duì)不確定的未來,以前,我們一般基于“變化的成本隨時(shí)間的推移而以指數(shù)方式上升”這一假設(shè),在項(xiàng)目開發(fā)的初期,想方設(shè)法的預(yù)測(cè)未來的種種變化,試圖抓住“不確定的未來”。但是,這常常是徒勞的。而且,這么做的結(jié)果,往往是把本來簡(jiǎn)單的需求復(fù)雜化了。其中的很多需求,根本就是客戶所不需要的。
?譬如說,現(xiàn)在很流行遠(yuǎn)程方法調(diào)用。很多程序員想當(dāng)然的認(rèn)為軟件將來需要提供遠(yuǎn)程調(diào)用這樣的功能。他們花了大力氣來使用復(fù)雜的CORBA,Web Services,RMI,EJB等技術(shù)提供遠(yuǎn)程調(diào)用功能,大大增加了軟件的開發(fā)成本,而實(shí)際上,大部分的軟件最后都沒有這樣的需求。而且,即使將來真的需要提供遠(yuǎn)程調(diào)用的功能,對(duì)于設(shè)計(jì)良好的軟件,到時(shí)候再增加這種功能也非難事。
?簡(jiǎn)單的軟件被搞復(fù)雜了,而客戶卻需要為并不需要的功能埋單!
?
融合XP的軟件開發(fā)過程
?如果我們?cè)谲浖_發(fā)中貫徹了那些開發(fā)健壯軟件的編程技術(shù),使我們的軟件比較健壯,也就是說符合“平滑成本曲線—變化的成本不會(huì)隨時(shí)間的推移而急劇上升”這一實(shí)踐XP的前提,那么我們就可以使用極限編程方法來開發(fā)軟件,提高軟件開發(fā)的生產(chǎn)效率和商業(yè)價(jià)值。
獲取需求
?傳統(tǒng)的軟件開發(fā)方法把這個(gè)階段稱作“需求調(diào)研”。因?yàn)?#xff0c;按照傳統(tǒng)的方法,是開發(fā)人員調(diào)查研究軟件的需求。而XP不同,XP是讓客戶在軟件開發(fā)人員的引導(dǎo)下為開發(fā)人員提供軟件的需求。因?yàn)?#xff0c;程序員對(duì)于開發(fā)軟件是專家,但卻不是軟件應(yīng)用領(lǐng)域的專家。顯然,對(duì)于軟件應(yīng)該是什么樣的,客戶是更有發(fā)言權(quán)的。軟件的需求,理所當(dāng)然應(yīng)該由客戶來提供。
?一、初步提出軟件需求
?客戶提出軟件應(yīng)該實(shí)現(xiàn)的功能,而開發(fā)人員則告訴客戶這些功能是否能夠?qū)崿F(xiàn),以及需要花費(fèi)多少成本。由客戶最后決定需要實(shí)現(xiàn)哪些功能。
?這個(gè)階段,客戶提出的需求,不必深入到細(xì)節(jié),只需要一個(gè)個(gè)粗略的需求。如果深入到細(xì)節(jié),那么就會(huì)陷入傳統(tǒng)開發(fā)方法過份需求調(diào)研和分析的陷阱中。
?XP實(shí)踐之:小版本—將一個(gè)簡(jiǎn)單系統(tǒng)迅速投入生產(chǎn),然后以很短的周期發(fā)布新版本。
?XP將整個(gè)軟件分成多個(gè)小版本,多階段發(fā)行。客戶根據(jù)功能的優(yōu)先級(jí)將功能劃分到多個(gè)版本中分批予以實(shí)現(xiàn)。但是,第一個(gè)版本,如果還沒有一個(gè)軟件的架構(gòu),那么這個(gè)版本需要實(shí)現(xiàn)哪些功能由開發(fā)人員決定。開發(fā)人員選擇那些便于搭建架構(gòu)的功能,比如用戶管理,權(quán)限管理等功能,首先實(shí)現(xiàn)。
?二、版本開發(fā)中的軟件需求
??? 初步提出的軟件需求是大致的,概括的,還沒有深入軟件需求的細(xì)節(jié)。進(jìn)入版本開發(fā)之后,我們需要客戶提供足夠的需求細(xì)節(jié),以支持“需求驅(qū)動(dòng)開發(fā)”。
?比如說,初步需求階段用戶管理這樣的需求,在版本開發(fā)階段,需要明確怎樣的用戶管理功能,比如,需要有增加、刪除、查看、修改用戶信息的功能。
?用UML表示,就是“用戶管理”用例。增加、刪除、查看、修改用戶這四個(gè)詳細(xì)的子用例,或者說是四個(gè)事件流。一旦程序員得到了確定的事件流,就可以以此為依據(jù),畫出UML的“序列圖”,驅(qū)動(dòng)軟件開發(fā)的展開。
?三、橫切公共關(guān)注
?開發(fā)人員從客戶提供的系統(tǒng)需求中,提取出公共的系統(tǒng)需求,譬如說,日志,訪問控制等。對(duì)于這些橫切公共關(guān)注,我們可以使用OOP的方式實(shí)現(xiàn)。編寫一個(gè)類來提供這些服務(wù),然后在各個(gè)業(yè)務(wù)模塊中通過“客戶—服務(wù)器”模式,調(diào)用這個(gè)類的方法來使用這些公共關(guān)注的服務(wù)。
?或者,更進(jìn)一步,使用AOP的方式實(shí)現(xiàn)。編寫方面來提供這些服務(wù)。通過“客戶—基礎(chǔ)設(shè)施”這樣的調(diào)用模式,在業(yè)務(wù)模塊并不知道的情況下,為它們提供這些服務(wù)。
測(cè)試驅(qū)動(dòng)開發(fā)和驗(yàn)收測(cè)試級(jí)重構(gòu)
?極限編程的哲學(xué)思想是實(shí)證主義的哲學(xué)思想:“任何不能度量的事物都是不存在的”[4]。極限編程反對(duì)傳統(tǒng)開發(fā)方法重視過程和文檔的做法,主張快速的開發(fā)、構(gòu)建、測(cè)試和部署軟件。認(rèn)為,一切決策都應(yīng)該基于軟件的實(shí)際運(yùn)行結(jié)果,而不是無端的猜測(cè)。經(jīng)常“問問電腦”[8],而不是基于一種毫無證據(jù)的信念,這是XP人的基本編程原則。
?XP人首先就需要是一個(gè)皈依實(shí)證主義哲學(xué)的信徒。
?因此,XP的開發(fā)方法和過程,可以用這樣的公式來表示:
?軟件=測(cè)試驅(qū)動(dòng)開發(fā)+重構(gòu)。
?一、驗(yàn)收測(cè)試驅(qū)動(dòng)開發(fā)
?怎樣才能夠證明開發(fā)人員已經(jīng)實(shí)現(xiàn)了一個(gè)客戶需要的功能呢?這就需要測(cè)試—驗(yàn)收測(cè)試。驗(yàn)收測(cè)試,是在用例實(shí)現(xiàn)之前由客戶在開發(fā)人員的幫助下編寫的。驗(yàn)收測(cè)試,就是一段文字,用來驗(yàn)證系統(tǒng)是否已經(jīng)滿足客戶需求。
?開發(fā)人員可以根據(jù)這段文字,用規(guī)范的腳本語(yǔ)言來形式化的定義。通常,我們?yōu)槊總€(gè)應(yīng)用程序編寫一套腳本語(yǔ)言。一般,我們使用XML文件的形式,或者是文本文件的形式。開發(fā)人員自己編寫一個(gè)簡(jiǎn)單的程序,讀取這個(gè)驗(yàn)收測(cè)試的文本文件,根據(jù)文本文件中的內(nèi)容,調(diào)用軟件中相應(yīng)的方法,予以執(zhí)行。腳本語(yǔ)言和具體的方法之間的對(duì)應(yīng)關(guān)系,由開發(fā)人員自己定義。
?實(shí)際上,驗(yàn)收測(cè)試[2],就是程序的一個(gè)用戶界面,是基于文本文件或者XML文件的一個(gè)用戶界面。是調(diào)用軟件核心業(yè)務(wù)邏輯的一個(gè)簡(jiǎn)單界面。通過它,我們能夠知道軟件的核心業(yè)務(wù)邏輯是否運(yùn)行正常。
?驗(yàn)收測(cè)試是程序,因此是可以運(yùn)行的。我們?cè)谲浖_發(fā)的過程中可以經(jīng)常運(yùn)行這些驗(yàn)收測(cè)試,以保證軟件一直處于正確的狀態(tài)。
?而且,驗(yàn)收測(cè)試沒有用戶界面,可以和單元測(cè)試一樣批量快速運(yùn)行,把結(jié)果保存到文件里。
?驗(yàn)收測(cè)試是關(guān)于一項(xiàng)軟件功能的最終可執(zhí)行文檔。程序員可以閱讀這些驗(yàn)收測(cè)試來真正的理解這些功能,從而明白應(yīng)該怎樣來實(shí)現(xiàn)這些功能。驗(yàn)收測(cè)試,本身就是“事件流”,程序員可以據(jù)此畫出“序列圖”,實(shí)現(xiàn)驗(yàn)收測(cè)試驅(qū)動(dòng)的軟件開發(fā)。
?通過驗(yàn)收測(cè)試,驅(qū)動(dòng)軟件開發(fā),是XP推薦的軟件開發(fā)方法。
?二、“客戶—服務(wù)器”模式層層委派開發(fā)軟件
??? 我們使用“客戶—服務(wù)器”模式,通過層層委派的方式來編寫軟件。
?序列圖描述了系統(tǒng)的業(yè)務(wù)模塊提供了哪些服務(wù),完成了事件流。我們可以直接將序列圖的邏輯編寫成控制器類(或者叫作“業(yè)務(wù)委派”, 所謂業(yè)務(wù)委派,是指在客戶端和業(yè)務(wù)服務(wù)層之間,增設(shè)一個(gè)“代表層”,所有客戶端到服務(wù)器端的調(diào)用,都“委托”該層完成。[10])。
?控制器就是我們核心業(yè)務(wù)邏輯的第一個(gè)客戶。它和業(yè)務(wù)服務(wù)類構(gòu)成了“客戶—服務(wù)器”關(guān)系,可能也會(huì)和領(lǐng)域模塊的領(lǐng)域模型類構(gòu)成“客戶—服務(wù)器”關(guān)系。
?為了實(shí)現(xiàn)用戶請(qǐng)求的功能,我們?cè)诳刂破髦姓{(diào)用幾個(gè)方法來實(shí)現(xiàn)這個(gè)用戶需求。但是,現(xiàn)在我們還沒有任何可以使用的方法。我們現(xiàn)在照樣在控制器中寫上要調(diào)用的方法,盡管它們還沒有被編寫和實(shí)現(xiàn)。沒關(guān)系,以后會(huì)實(shí)現(xiàn)的!
?將這些方法按照邏輯和是否會(huì)在客戶端造成重復(fù)為標(biāo)準(zhǔn),分配給各個(gè)模塊的各個(gè)類。按照“針對(duì)接口”編程的原則,將這些服務(wù)分發(fā)到各個(gè)接口中去,而不是實(shí)現(xiàn)類。
?我們可以使用CRC圖來幫助完成這個(gè)分配方法的工作。
?現(xiàn)在,雖然程序還沒有開發(fā)完成,但我們已經(jīng)知道程序在邏輯上已經(jīng)完成了,或者說,已經(jīng)設(shè)計(jì)完成了。我們只剩下兩項(xiàng)簡(jiǎn)單的實(shí)現(xiàn)層面的工作要做:1,編寫接口的實(shí)現(xiàn)類,實(shí)現(xiàn)所需的方法;2,利用重構(gòu),將這些方法移到最適合的接口和實(shí)現(xiàn)類中去。
?三、驗(yàn)收測(cè)試級(jí)重構(gòu)
?重構(gòu),就是“在不改變代碼外在行為的前提下,對(duì)代碼做出修改,以改進(jìn)程序的內(nèi)在結(jié)構(gòu)”[5]。一旦編寫好代碼,對(duì)代碼的任何修改,都可以算是重構(gòu)。
?重構(gòu)總是和測(cè)試聯(lián)系在一起的。沒有自動(dòng)化測(cè)試的支持,是不可能進(jìn)行有效的重構(gòu)的。按照所需要的測(cè)試,重構(gòu)可以分為兩類:
?一是“單元測(cè)試級(jí)的重構(gòu)”,也是大家使用最為廣泛的重構(gòu)。但是,這類重構(gòu)改善的只是軟件局部的質(zhì)量,一般就是一兩個(gè)類的改善。
?另一種是“驗(yàn)收測(cè)試級(jí)的重構(gòu)”,這是改善整個(gè)模塊的質(zhì)量的重構(gòu)。它是對(duì)整個(gè)模塊實(shí)現(xiàn)機(jī)制的改善,常常涉及到多個(gè)接口和類的修改。
?在需求變化的過程中,依靠自動(dòng)化的驗(yàn)收測(cè)試,我們可以很容易的進(jìn)行驗(yàn)收級(jí)重構(gòu),快速地響應(yīng)軟件需求的變化。也可以使軟件變得越來越健壯。
單元測(cè)試驅(qū)動(dòng)開發(fā)和單元測(cè)試級(jí)重構(gòu)
?在編寫具體的類時(shí),我們也使使用“單元測(cè)試驅(qū)動(dòng)開發(fā)”的。首先編寫測(cè)試類,然后再編寫實(shí)現(xiàn)類,逐個(gè)方法的完成單元測(cè)試,最后完成類的編寫。在需要改善類的實(shí)現(xiàn)代碼時(shí),使用單元測(cè)試級(jí)的重構(gòu),改善類的內(nèi)部結(jié)構(gòu)。
?XP開發(fā)軟件,就是自頂向下,一層層的由測(cè)試驅(qū)動(dòng)開發(fā),在需要改善使,使用這些測(cè)試安全的進(jìn)行重構(gòu)。
小結(jié)
?最后,整個(gè)軟件的開發(fā)工作和軟件質(zhì)量,都可以通過驗(yàn)收測(cè)試和單元測(cè)試得到保障和度量。客戶可以隨時(shí)監(jiān)控軟件開發(fā)的進(jìn)展情況。客戶花的錢可以直接看到成果。而且,通過小版本快速迭代的發(fā)布,客戶可以及早的享用軟件開發(fā)的成果,也可以即時(shí)地終止不合算的軟件項(xiàng)目,而不會(huì)造成投入全部付之東流。
???
使用Java開發(fā)企業(yè)級(jí)應(yīng)用的參考架構(gòu)
?現(xiàn)在,讓我們使用Java實(shí)際的開發(fā)兩個(gè)企業(yè)級(jí)應(yīng)用的參考架構(gòu)。這個(gè)架構(gòu)并不實(shí)現(xiàn)特定的業(yè)務(wù)需求,僅僅是兩個(gè)開發(fā)企業(yè)級(jí)應(yīng)用的架構(gòu)。這個(gè)架構(gòu)是健壯的,易于修改的,也是符合“平滑成本曲線—變化的成本不會(huì)隨時(shí)間的推移而急劇上升”這一XP要求的。
JavaEE
?Java EE,Java平臺(tái)企業(yè)版(Java Platform Enterprise Edition), 是Sun公司為企業(yè)級(jí) 應(yīng)用推出的標(biāo)準(zhǔn)平臺(tái)。Java平臺(tái)共分為三個(gè)主要版本Java EE、Java SE和Java ME。原來,它們叫作J2EE、J2SE、J2ME。
?Java EE是使用Java進(jìn)行企業(yè)開發(fā)的一套擴(kuò)展標(biāo)準(zhǔn)。Java EE平臺(tái)提供了多層、分布式的應(yīng)用模型,重新利用組件的能力,統(tǒng)一安全的模式以及靈活的處理控制能力。Java EE包括EJB, JTA, JDBC, JCA, JMX, JNDI, JMS, JavaMail, Servlet, JSP等規(guī)范[10]。
“經(jīng)典”的JavaEE架構(gòu)
?通常,人們認(rèn)為Java EE是一種分布式平臺(tái),EJB在該平臺(tái)中占有中心地位。“經(jīng)典”的Java EE架構(gòu)是這樣的[7]:
?1,Java類分散在不同的JVM中,雖然要想把這些類并置在一起、消除遠(yuǎn)程調(diào)用的程中負(fù)載也是可以辦到的。
?2,Web層一般由一個(gè)MVC架構(gòu)實(shí)現(xiàn)(其他架構(gòu)方案也都是如此)。如果應(yīng)用系統(tǒng)設(shè)計(jì)的比較精心,那么就會(huì)有一個(gè)專門的客戶端代理層,通過它調(diào)用遠(yuǎn)程EJB,這樣就能夠干凈利落的解除web層和EJB之間的耦合。
?3,所有的業(yè)務(wù)對(duì)象都是帶遠(yuǎn)程接口的無狀態(tài)session bean,運(yùn)行在EJB容器中。EJB容器提供遠(yuǎn)程調(diào)用機(jī)制、事物管理、線程管理,可能還有基于角色的安全服務(wù)。
?4,所有的數(shù)據(jù)訪問都要通過entity bean。Entity bean提供了O-R映射。
?5,EIS層由一個(gè)或多個(gè)數(shù)據(jù)庫(kù)或者遺留系統(tǒng)組成。如果存在多個(gè)帶事務(wù)的資源,通過EJB容器訪問的JTA服務(wù)會(huì)負(fù)責(zé)協(xié)調(diào)分布式服務(wù)。
?對(duì)于不準(zhǔn)備提供EJB集群服務(wù)或者遠(yuǎn)程調(diào)用EJB的企業(yè)級(jí)應(yīng)用,也可以使用本地EJB。
?Java EE的這種架構(gòu)提供了強(qiáng)大的遠(yuǎn)程調(diào)用能力、JTA聲明式事務(wù)和聲明式安全等服務(wù),以及集群部署EJB的能力。從而使Java在短短幾年時(shí)間內(nèi)占領(lǐng)企業(yè)級(jí)應(yīng)用市場(chǎng)。
?但是,這種架構(gòu)在多年的實(shí)踐中,被發(fā)現(xiàn)了不少弱點(diǎn):
?1,這種架構(gòu)開發(fā)起來十分困難和低效。這主要是EJB規(guī)范太過復(fù)雜。按照EJB2.1規(guī)范的定義,EJB組件必須事先很多的接口, 比如Home接口、Remote接口、local 接口,等等.還要針對(duì)各種應(yīng)用類型定義許多的xml描述文件。當(dāng)我們需要訪問某個(gè)組件或者服務(wù)的時(shí)候,必須通過JDNI查找,通過名字綁定服務(wù),才能找到我們需要的對(duì)象。
?2,運(yùn)行起來也是十分低效的。Entity Bean的實(shí)現(xiàn)機(jī)制有問題,導(dǎo)致了Entity Bean訪問數(shù)據(jù)庫(kù)十分低效。甚至,目前很少有人采用Entity Bean來訪問數(shù)據(jù)庫(kù)。但是,如果不使用Entity Bean,Session Bean
使用其他數(shù)據(jù)庫(kù)訪問方式,則會(huì)存在一些問題。因?yàn)橛行?shù)據(jù)庫(kù)訪問機(jī)制,如Hibernate的Session Factory必須是單個(gè)實(shí)例的,而Session Bean中是無法實(shí)現(xiàn)單例的。
?另外,EJB的執(zhí)行效率也比POJO慢。
??? 過去,人們認(rèn)為EJB是Java EE的核心。但是,實(shí)際上,所有的Java EE服務(wù),包括JTA,RMI,JNDI,JMS等,都可以脫離EJB容器來使用,它們實(shí)際上都是Web容器提供的功能,EJB容器只是提供了一種訪問這些服務(wù)的方法而已,我們完全可以不通過EJB而直接使用這些Java EE服務(wù)。
?Java開源軟件
?Java是一種開放的技術(shù),它的發(fā)展,既由官方的JCP(Java Community Process)組織通過制定規(guī)范推動(dòng),也由Java開源社區(qū)推動(dòng)。戰(zhàn)斗在一線的程序員,開發(fā)出了很多開源的Java軟件,這些軟件得到了很大的應(yīng)用,極大地推動(dòng)了Java的發(fā)展。我將使用這些開源軟件,結(jié)合Java EE提供的服務(wù),不使用EJB,來開發(fā)企業(yè)級(jí)軟件。
?Struts是一種Web層開發(fā)框架,它是使用MVC模式實(shí)現(xiàn)的。目前,它是JavaEE領(lǐng)域中主流的Web層開發(fā)技術(shù),不論使用EJB還是其他技術(shù),一般都使用它來編寫Web層。
?Ant是java世界事實(shí)上的標(biāo)準(zhǔn)構(gòu)建工具。它可以執(zhí)行一系列的自動(dòng)化任務(wù),包括構(gòu)建項(xiàng)目,運(yùn)行軟件和自動(dòng)化運(yùn)行單元測(cè)試等。
?JUnit是XP創(chuàng)始人Kent Beck開發(fā)的一個(gè)單元測(cè)試工具。可以使用Ant自動(dòng)化運(yùn)行Junit測(cè)試。
?Hibernate則是事實(shí)上的O-R映射的標(biāo)準(zhǔn)框架。它通過xml文件配置,來映射數(shù)據(jù)庫(kù)表和Java程序中的POJO對(duì)象,通過同步緩存和數(shù)據(jù)庫(kù),自動(dòng)更新數(shù)據(jù)庫(kù)。它使我們可以像使用對(duì)象型數(shù)據(jù)庫(kù)那樣使用關(guān)系型數(shù)據(jù)庫(kù)。
?Spring框架,是一個(gè)輕量級(jí)的應(yīng)用框架。Spring的目標(biāo)就是提供一中貫穿始終的解決方案,將各種專用框架整合成一個(gè)連貫的整體。它可以整合Struts、Hibernate等框架,甚至簡(jiǎn)化EJB的開發(fā)和訪問。
?Spring的威力來源于兩個(gè)方面:IOC容器和AOP框架。使用Spring框架,完全可以取代EJB的功能。
?這些開源框架,可以幫助我們實(shí)現(xiàn)非EJB的企業(yè)級(jí)應(yīng)用。
不用EJB的簡(jiǎn)單Java EE架構(gòu)
?對(duì)于簡(jiǎn)單的企業(yè)級(jí)應(yīng)用,我們可以使用簡(jiǎn)單的技術(shù)實(shí)現(xiàn)。這種架構(gòu),雖然簡(jiǎn)單,但仍然很健壯。
?使用Struts作為Web層開發(fā)技術(shù),使用POJO作為業(yè)務(wù)對(duì)象,使用DAO模式封裝的JDBC作為數(shù)據(jù)訪問層技術(shù)。通過DAO模式,我們也實(shí)現(xiàn)了簡(jiǎn)單的O-R映射。
?一個(gè)典型的客戶用例的實(shí)現(xiàn)模塊中,我們的類分為以下幾個(gè)部分:
?1,Struts的控制器模塊Action,實(shí)際上,也就是“業(yè)務(wù)委派”模式的業(yè)務(wù)委派類。它們從視圖中接收用戶請(qǐng)求,然后調(diào)用業(yè)務(wù)服務(wù)類的方法實(shí)現(xiàn)用戶的請(qǐng)求。
??? Action和Servlet一樣,都是單例。也就是說,一臺(tái)機(jī)器上只有一個(gè)Action類的實(shí)例,為所有的客戶服務(wù)。也就是說,它不能夠保存客戶的狀態(tài),因?yàn)闋顟B(tài)可以保存在HttpRequest、HttpSession、ServletContext里。
?2,Struts的MVC模式中的Model—ActionForm,它用于在Action和視圖之間傳遞數(shù)據(jù)。ActionForm中的數(shù)據(jù),如果作為表單提交,需要是String型的。
?3,領(lǐng)域模塊的領(lǐng)域模型類。它是O-R映射中代表數(shù)據(jù)庫(kù)表的對(duì)象。也作為參數(shù)和返回值在控制器Action類和業(yè)務(wù)服務(wù)Service類之間,以及Service類和DAO類之間傳遞數(shù)據(jù)。
?在Action中,Action從視圖中拿到的數(shù)據(jù)容器類ActionForm并不能夠直接調(diào)用業(yè)務(wù)服務(wù)Service類的方法,必須要轉(zhuǎn)換為領(lǐng)域模型對(duì)象,或者簡(jiǎn)單對(duì)象才能夠作為參數(shù)調(diào)用業(yè)務(wù)服務(wù)Service類的方法。
?Service類的方法返回的參數(shù),也必須要封裝到ActionForm中才能夠讓視圖拿到顯示給用戶。
?4,業(yè)務(wù)模塊的業(yè)務(wù)服務(wù)Service接口及其實(shí)現(xiàn)類
?Service接口中封裝了Action要調(diào)用的所有業(yè)務(wù)操作。其實(shí)現(xiàn)類實(shí)現(xiàn)了這個(gè)接口。Service實(shí)現(xiàn)類是POJO,而不是session bean。
?Service類中要與數(shù)據(jù)庫(kù)交互的操作,都委派給DAO接口的實(shí)現(xiàn)類。
?Service實(shí)現(xiàn)類也應(yīng)該是單例的,因?yàn)樗皇翘峁┓?wù),并不需要保存狀態(tài)。所有的數(shù)據(jù)都可以通過方法的參數(shù)傳遞進(jìn)來。
?要實(shí)現(xiàn)單例,我們可以使用一個(gè)工廠類,也可以直接在Service實(shí)現(xiàn)類里提供單例的實(shí)現(xiàn)方法。這里,為了簡(jiǎn)單,我在Service實(shí)現(xiàn)類里直接實(shí)現(xiàn)了單例。
public class JDBCMessageServiceImpl implements IMessageService{
?private static JDBCMessageServiceImpl instance=null;
?/**
* @return JDBCMessageServiceImpl 返回這個(gè)Service類的一個(gè)實(shí)例
??? */
?? public static JDBCMessageServiceImpl getInstance(){
??? if(null==JDBCMessageServiceImpl.instance){
???? JDBCMessageServiceImpl.instance=new JDBCMessageServiceImpl();
????? }
??????? return instance;
?? }
……
?5,領(lǐng)域模塊的數(shù)據(jù)訪問對(duì)象DAO接口及其實(shí)現(xiàn)類
??? DAO接口中封裝了持久化領(lǐng)域模型類的所有方法。通過DAO,我們實(shí)現(xiàn)了內(nèi)存中的領(lǐng)域模型類和硬盤上的數(shù)據(jù)庫(kù)表之間的映射。這里,DAO的實(shí)現(xiàn)類是使用JDBC方式訪問數(shù)據(jù)庫(kù)的類。
?DAO實(shí)現(xiàn)類一樣是提供數(shù)據(jù)訪問服務(wù)的服務(wù)類,也應(yīng)該是單例的。我們使用和Service類一樣的方式實(shí)現(xiàn)單例。
?在DAO實(shí)現(xiàn)類中,我們盡量使用領(lǐng)域模型類作為參數(shù)和返回值。這樣,就實(shí)現(xiàn)了領(lǐng)域模型類和數(shù)據(jù)庫(kù)表之間的O-R映射。在DAO實(shí)現(xiàn)類之外,我們可以使用領(lǐng)域模型類來操作數(shù)據(jù)庫(kù)表中的數(shù)據(jù)。
?這個(gè)架構(gòu)中,我們?cè)赟ervice模塊和DAO模塊中都使用了接口—實(shí)現(xiàn)類這種方式。對(duì)于同一個(gè)接口,可以有無數(shù)個(gè)實(shí)現(xiàn)類。
?這里,我僅僅使用JDBC這一種方式實(shí)現(xiàn)DAO接口。實(shí)際上,我們可以很方便的編寫一個(gè)使用iBatis,Hibernate或者JDO等其他數(shù)據(jù)訪問技術(shù)的DAO實(shí)現(xiàn)類。
?另外,我們可以使用獨(dú)立于Web容器的連接池。
?我們可以使用Web容器的JNDI將實(shí)現(xiàn)連接池的數(shù)據(jù)源發(fā)布到JNDI上,客戶代碼從JNDI上得到數(shù)據(jù)源的實(shí)例,調(diào)用連接池中的連接。但是,不同的Web容器發(fā)布JNDI是很不一致的,而且獲得JNDI也是非常費(fèi)事的。
?我們可以用Apache的DBCP框架,直接編寫連接池。這不需要任何Web容器的支持,可以在一般的Java程序中使用。
?如果需要遠(yuǎn)程調(diào)用,我們可以在應(yīng)用中集成Axis,提供Web Servics。
?如果需要集群部署,我們也可以在多臺(tái)服務(wù)器上部署多個(gè)Web應(yīng)用程序。
?一、這個(gè)架構(gòu)的優(yōu)點(diǎn)
?1,不需要EJB容器,只需要Web容器即可,降低了成本。
?2,不需要EJB那樣累贅的部署描述符。
?3,容易在不同的Web容器和應(yīng)用服務(wù)器之間切換。
?4,實(shí)現(xiàn)更加簡(jiǎn)單,業(yè)務(wù)服務(wù)Service和數(shù)據(jù)訪問對(duì)象DAO都是簡(jiǎn)單的POJO類。
?二、這個(gè)架構(gòu)的缺點(diǎn)
?1,這里使用了直接的單例模式來生成單例,而不是使用容器管理來實(shí)現(xiàn)單例,代碼之間因而有一定的耦合。
?2,同樣,由于沒有容器管理對(duì)象的實(shí)例。所以,我們必須在客戶代碼中手工的調(diào)用接口實(shí)現(xiàn)類的創(chuàng)建單例的方法。這樣,在代碼中就出現(xiàn)了一定的耦合。如果需要改變實(shí)現(xiàn)類,我們就必須修改調(diào)用實(shí)現(xiàn)類的客戶的源代碼。
如,在Action中:
?ImessageService messageService=JDBCMessageServiceImpl.getInstance();
?另外,在Service實(shí)現(xiàn)類中,也需要得到DAO實(shí)現(xiàn)類的單例:
?public class JDBCMessageServiceImpl implements IMessageService{
?private IMessageDao messageDao=null;
?? public JDBCMessageServiceImpl() {
???? /*
????? 由于沒有元數(shù)據(jù)配置,所以,必須要在構(gòu)造器中直接傳入類的實(shí)例。
????? */
???? setMessageDao(JDBCMessageDaoImpl.getInstance());
?? }
?……
?3,我們必須手工編寫事務(wù),不能夠使用EJB容器提供的容器管理事務(wù)CMT。EJB的CMT允許用戶在配置文件中聲明使用事務(wù),而不需要手工編寫事務(wù)。
?4,不能夠使用EJB提供的對(duì)Session Bean,Entity Bean的聲明性訪問控制。EJB提供的訪問控制,可以控制EJB方法的訪問權(quán)。
?當(dāng)然,這個(gè)架構(gòu)一樣可以通過Web Services的訪問控制機(jī)制實(shí)現(xiàn)這一點(diǎn)。而且,EJB雖然提供了聲明性訪問控制的機(jī)制,但是由于各個(gè)EJB容器都有不同的配置方法,移植起來十分困難,所以也很少使用。
?總之,這個(gè)架構(gòu)是簡(jiǎn)單、健壯、功能強(qiáng)大的。對(duì)于小規(guī)模的企業(yè)級(jí)應(yīng)用完全可以使用這種簡(jiǎn)單的架構(gòu)。
?對(duì)于更大規(guī)模的企業(yè)級(jí)軟件,也許使用更加強(qiáng)大、復(fù)雜的技術(shù)框架,效率更高。比如說,使用Hibernate技術(shù)提供DAO實(shí)現(xiàn)類,或者更進(jìn)一步,使用Spring框架來管理事務(wù)和業(yè)務(wù)對(duì)象的單例。
?由于這個(gè)架構(gòu)在業(yè)務(wù)服務(wù)模塊Service和數(shù)據(jù)訪問模塊DAO中使用了接口和實(shí)現(xiàn)分離的方式,增加Spring、Hibernate等框架都是非常容易的。這也正反映了這個(gè)架構(gòu)的健壯之處。
使用“輕量級(jí)容器”的Java EE架構(gòu)
?“輕量級(jí)容器”,是指提供了管理、定位業(yè)務(wù)對(duì)象的方法。一般就是指IOC反轉(zhuǎn)控制容器。它實(shí)際上是提供了一個(gè)Dictionary類或相似的類,也就是名—值對(duì)的集合。在容器啟動(dòng)時(shí),它生成類的實(shí)例,然后放在這個(gè)字典內(nèi),當(dāng)應(yīng)用程序中需要這個(gè)類的實(shí)例時(shí),如果聲明為單例,就從這個(gè)字典內(nèi)按照名字取出對(duì)象。如果聲明為多例模式,容器就生成一個(gè)新的實(shí)例返回給客戶代碼。
?這樣就實(shí)現(xiàn)了單例。不再需要像上面那樣在一般的POJO類中編寫實(shí)現(xiàn)單例的代碼了。
?而且,現(xiàn)在,調(diào)用代碼中就只需要接口,不需要具體的實(shí)現(xiàn)類了。我們可以在配置文件中改變提供的實(shí)現(xiàn)類,不需要再修改源代碼了。
?Spring就是這樣一個(gè)輕量級(jí)的容器。Java EE的JNDI實(shí)際上也可以看作是一個(gè)容器。它是一個(gè)注冊(cè)表,也就是“名—值”對(duì)的集合。不同的是,它把這些對(duì)象放在了網(wǎng)上進(jìn)行遠(yuǎn)程發(fā)布。訪問JNDI對(duì)象是十分麻煩的。
?另外,Spring還提供了AOP框架,這可以讓它提供聲明式的服務(wù),如,EJB容器那樣的聲明式事務(wù)等。而且,比EJB容器更加強(qiáng)大,可以自己編寫方面。
?Spring的IOC容器和AOP框架,讓它完全能夠替代EJB容器。比EJB容器更簡(jiǎn)單,更強(qiáng)大。
?當(dāng)然,還存在其他的IOC容器和AOP框架,但是目前只有Spring結(jié)合了這兩者。所以,這里我使用Spring來開發(fā)架構(gòu)。
?一、使用“輕量級(jí)容器”的Java EE架構(gòu)
?這個(gè)架構(gòu)和前面那個(gè)不用EJB的簡(jiǎn)單Java EE架構(gòu)十分類似。這里,我們給予前面的架構(gòu)進(jìn)行修改,將它變?yōu)槭褂谩拜p量級(jí)容器”的Java EE架構(gòu)。
?Web層,我們?nèi)匀皇褂肧truts框架。業(yè)務(wù)服務(wù)模塊Service和數(shù)據(jù)訪問模塊DAO仍然使用POJO和接口—實(shí)現(xiàn)類分離的風(fēng)格。在DAO的實(shí)現(xiàn)類中,我們?yōu)榱搜菔綡ibernate,所以增加了Hibernate的Dao實(shí)現(xiàn)類。當(dāng)然,原來的基于JDBC的實(shí)現(xiàn)類一樣可用。
?首先,使用Spring以后,借助于Spring 的IOC容器,我們沒有必要再在Service和DAO實(shí)現(xiàn)類中提供創(chuàng)建單例的方法了,也沒有必要再在Service的實(shí)現(xiàn)類的默認(rèn)構(gòu)造其中創(chuàng)建DAO實(shí)現(xiàn)類的單例了。只需要空的構(gòu)造器,然后再在配置文件中配置屬性,將DAO實(shí)現(xiàn)類的實(shí)例通過set方法傳進(jìn)來即可。
?在Struts的Action類中,也不再需要直接用Service實(shí)現(xiàn)類的單例方法來獲得單例了,而是通過Spring的getBean(“*”)方法從Spring的注冊(cè)表中得到Service實(shí)現(xiàn)類的單例。即使實(shí)現(xiàn)類改變,也不需要改變?cè)创a,只需要修改Spring的xml配置文件即可。
?Spring提供了直接集成Hibernate的方法。可以將Hibernate的SessionFactory對(duì)象也置于SpringIOC容器的管理下,生成單例。
?Spring直接提供了幾個(gè)類,可以在xml文件中通過配置,聲明式的提供事務(wù)。不僅可以為Hibernate提供事務(wù),也可以為我們前面架構(gòu)中的JDBC實(shí)現(xiàn)提供聲明式的事務(wù)。如果我們使用的應(yīng)用服務(wù)器提供JTA事務(wù)支持,Spring也可以通過聲明的方式提供JTA事務(wù)。
?另外,Spring還有一個(gè)子項(xiàng)目,Acegi Security System for Spring。這個(gè)框架是使用Spring AOP框架和Web容器的Filter機(jī)制,再加上本地線程變量ThreadLocal實(shí)現(xiàn)的一種安全機(jī)制。它在保護(hù)Web資源時(shí),使用Web容器標(biāo)準(zhǔn)的Filter過濾器機(jī)制,在保護(hù)POJO類時(shí),使用Spring AOP的聲明式保護(hù),可以保護(hù)任何Spring管理的POJO的方法。這比EJB的聲明式安全機(jī)制更加強(qiáng)大,EJB只能夠保護(hù)EJB組件。
?但是,Acegi的安全機(jī)制比較復(fù)雜。如果是B/S應(yīng)用,僅僅需要提供對(duì)Web資源的保護(hù),建議不要使用它。它更適合對(duì)遠(yuǎn)程調(diào)用的組件(如,Web Services,RMI,EJB,Hessian,Burlap和Spring Http Invoker等)的方法級(jí)訪問控制。
?二、這個(gè)架構(gòu)的優(yōu)點(diǎn)
?1,架構(gòu)簡(jiǎn)單,但是功能強(qiáng)大,甚至比使用重量級(jí)的EJB容器的經(jīng)典架構(gòu)更加強(qiáng)大。
?2,這種架構(gòu)和不用EJB的簡(jiǎn)單架構(gòu)一樣,都可以通過部署多個(gè)應(yīng)用程序,來實(shí)現(xiàn)集群部署。
?3,與不用EJB的簡(jiǎn)單架構(gòu)相比,本方案使用了一個(gè)輕量級(jí)的容器,這樣做并沒有增加多少?gòu)?fù)雜性,反而是減少了代碼,降低了復(fù)雜性。與EJB容器相比,當(dāng)然是更加容易學(xué)習(xí)和配置。
?4,如果輕量級(jí)容器提供了AOP,那么就能夠提供比EJB更加強(qiáng)大的基礎(chǔ)設(shè)施和聲明式服務(wù)。
?5,這種架構(gòu)不需要EJB容器。
?6,Spring已經(jīng)提供了很多類,讓我們可以簡(jiǎn)單的訪問很多Java EE服務(wù),如JNDI,屬性文件等,都可以簡(jiǎn)單的通過配置來完成。它還對(duì)一些常用的框架提供了支持,如iBatis,Hibernate等。通過Spring提供的Hibernate支持類,我們可以簡(jiǎn)單的使用Hibernate,代碼量要比直接使用Hibernate少很多!
?7,由于我們不使用EJB容器的服務(wù),也很少使用Web容器提供的服務(wù),因此,我們的應(yīng)用程序可以方便的在不用的應(yīng)用服務(wù)器間移植。
?8,IOC可以讓輕量級(jí)服務(wù)器為你“組裝”對(duì)象。也就是說應(yīng)用程序中不在需要編寫尋找或者生成合作對(duì)象的代碼了。比如說,Service實(shí)現(xiàn)類中已經(jīng)不需要再生成DAO實(shí)現(xiàn)類的實(shí)例的代碼了。
?9,沒有這些組裝對(duì)象的代碼,我們?nèi)匀豢梢允褂肑Unit來測(cè)試我們的應(yīng)用程序。因?yàn)檩p量級(jí)框架可以在任何Java程序中運(yùn)行。我們可以在Junit測(cè)試類中創(chuàng)建和使用Spring等輕量級(jí)容器。
?
總結(jié)
“源代碼就是設(shè)計(jì)”
?1992年,Jack W.Reeves寫了一篇論文《什么是軟件設(shè)計(jì)》,答案是“源代碼就是設(shè)計(jì)”![2]
?源代碼編譯的過程,就是軟件實(shí)現(xiàn)的過程,而源代碼編寫的過程,就是軟件設(shè)計(jì)的過程。至于我們通常所說的分析軟件和設(shè)計(jì)軟件,只是對(duì)軟件設(shè)計(jì)過程之中的更高層次的分析和設(shè)計(jì)過程。它們分析和設(shè)計(jì)的仍然是源代碼這個(gè)真正的最終的設(shè)計(jì)文檔。
?源代碼,有兩類讀者,一類是編譯器,它將源代碼編譯成可以運(yùn)行的軟件,它對(duì)于源代碼的唯一要求,就是符合語(yǔ)言規(guī)范,沒有語(yǔ)法錯(cuò)誤。另一類,就是軟件的開發(fā)人員,他們需要經(jīng)常地閱讀和修改源代碼。他們是普通的人類,只能夠理解組織優(yōu)良,邏輯清晰的文字。顯然,源代碼最重要的讀者顯然是這些挑剔的人類。
??? 源代碼就是設(shè)計(jì)文檔,就是一篇關(guān)于軟件怎樣組織的說明文章。你讀過幾十萬(wàn)行字的書嗎?如果你要寫一本幾十萬(wàn)行字的書,那么怎樣才能夠讓讀者能夠很容易的閱讀、理解、然后修改呢?
?寫這樣一本書的要求,就是寫代碼的要求!顯然,我們編寫的代碼,應(yīng)該組織合理,賦予邏輯,有足夠的抽象層次,能夠讓讀者一步步地深入到細(xì)節(jié),而不是直接把細(xì)節(jié)全部拋給讀者。
?章,節(jié),段,不是很像源代碼的包、類和方法嗎?
?健壯的軟件,就是開發(fā)者容易閱讀、理解,然后修改的軟件;就是結(jié)構(gòu)簡(jiǎn)單,邏輯清晰的軟件;就是經(jīng)過了層層抽象的軟件,每一層都是結(jié)構(gòu)簡(jiǎn)單,邏輯清晰的!
?軟件技術(shù)的發(fā)展,就是對(duì)源代碼抽象能力的發(fā)展!
總結(jié)
?本文所論述的這些編程技術(shù)和軟件開發(fā)方法,不僅對(duì)于開發(fā)健壯的企業(yè)級(jí)應(yīng)用有積極的意義,對(duì)開發(fā)所有類型健壯的軟件也一樣有意義。學(xué)習(xí)和實(shí)踐這些編程技術(shù)和開發(fā)方法,將會(huì)有效的提高我們編寫的軟件的質(zhì)量和商業(yè)價(jià)值。
?
??????????????? 致? 謝
?
?本文是在劉文予教授的悉心指導(dǎo)下完成的。劉教授在理論、方法和實(shí)踐中都給予了我莫大的指導(dǎo)和幫助,使我在理論和實(shí)踐上有了很大的提高,順利完成了本篇論文。在此,謹(jǐn)向?qū)熤乱灾孕牡母兄x。
?我還要感謝公司里的同事。
?還有James Gosling,感謝他為這個(gè)世界帶來了Java這門我最鐘愛的編程語(yǔ)言。Java改變了我的一生!
?同時(shí),還要感謝我在華中科技大學(xué)求學(xué)期間,對(duì)我給予關(guān)心,幫助的所有老師和同學(xué)。
?最后,再次對(duì)所有關(guān)心、幫助我的老師、同學(xué)和同事表示衷心地感謝!
?
?
?????????????? 參 考 文 獻(xiàn)
?
?[1]? Martin Fowler. 企業(yè)應(yīng)用架構(gòu)模式. 王懷民 周斌 譯. 北京。機(jī)械工業(yè)出版社,2005年.
?[2]? Robert C.Martin. 敏捷軟件開發(fā):原則、模式與實(shí)踐. 鄧輝 譯. 北京。清華大學(xué)出版社,? 2003年.
?[3]? Erich Gamma, Richard Helm, Ralph Johnson, et al. 設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ). 李英軍,馬曉星,蔡敏等 譯. 北京。機(jī)械工業(yè)出版社, 2005年.
?[4]? Rod Johnson.? J2EE設(shè)計(jì)開發(fā)編程指南. 魏海萍 譯. 北京。 電子工業(yè)出版社, 2003年.
?[5]? Martin Fowler. 重構(gòu)—改善既有代碼的設(shè)計(jì). 侯捷,熊節(jié) 譯.? 北京。 中國(guó)電力出版社, 2003年.
?[6]? Kent Beck. 解析極限編程—擁抱變化. 唐東銘 譯. 北京。人民郵電出版社, 2002年.
?[7] Rod Johnson, Juergen Hoeller.? Expert One-on-One J2EE Development without EJB中文版.? JavaEye 譯. 北京。 電子工業(yè)出版社, 2005年.
?[8]? Iver Jacoboson, Pan-Wei Ng.? AOSD中文版—基于用例的面向方面軟件開發(fā).? 徐鋒 譯.? 北京。 電子工業(yè)出版社, 2005年.? .77-85
?[9]? Deepak Alur, John Crupi, Dan Malks.? J2EE核心模式、第二版.
???? 北京。 機(jī)械工業(yè)出版社, 2005年.? .218-227
?[10]? Mark Cade, Simon Roberts.? J2EE架構(gòu)師認(rèn)證指南. 武欣,羅云峰,劉侃 譯.? 北京。 機(jī)械工業(yè)出版社, 2004年.? .15-17
轉(zhuǎn)載于:https://www.cnblogs.com/armlinux/archive/2006/06/23/2391091.html
總結(jié)
以上是生活随笔為你收集整理的开发健壮的企业级应用的研究的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclipse中用户库的使用
- 下一篇: c# 添加图片水印,可以指定水印位置+生