五 PetShop之業(yè)務(wù)邏輯層設(shè)計(jì)
業(yè)務(wù)邏輯層(Business Logic Layer)無疑是系統(tǒng)架構(gòu)中體現(xiàn)核心價(jià)值的部分。它的關(guān)注點(diǎn)主要集中在業(yè)務(wù)規(guī)則的制定、業(yè)務(wù)流程的實(shí)現(xiàn)等與業(yè)務(wù)需求有關(guān)的系統(tǒng)設(shè)計(jì),也即是說它是與系統(tǒng)所應(yīng)對(duì)的領(lǐng)域(Domain)邏輯有關(guān),很多時(shí)候,我們也將業(yè)務(wù)邏輯層稱為領(lǐng)域?qū)?。例如Martin Fowler在《Patterns of Enterprise Application Architecture》一書中,將整個(gè)架構(gòu)分為三個(gè)主要的層:表示層、領(lǐng)域?qū)雍蛿?shù)據(jù)源層。作為領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的先驅(qū)Eric Evans,對(duì)業(yè)務(wù)邏輯層作了更細(xì)致地劃分,細(xì)分為應(yīng)用層與領(lǐng)域?qū)樱ㄟ^分層進(jìn)一步將領(lǐng)域邏輯與領(lǐng)域邏輯的解決方案分離。
業(yè)務(wù)邏輯層在體系架構(gòu)中的位置很關(guān)鍵,它處于數(shù)據(jù)訪問層與表示層中間,起到了數(shù)據(jù)交換中承上啟下的作用。由于層是一種弱耦合結(jié)構(gòu),層與層之間的依賴是向下的,底層對(duì)于上層而言是“無知”的,改變上層的設(shè)計(jì)對(duì)于其調(diào)用的底層而言沒有任何影響。如果在分層設(shè)計(jì)時(shí),遵循了面向接口設(shè)計(jì)的思想,那么這種向下的依賴也應(yīng)該是一種弱依賴關(guān)系。因而在不改變接口定義的前提下,理想的分層式架構(gòu),應(yīng)該是一個(gè)支持可抽取、可替換的“抽屜”式架構(gòu)。正因?yàn)槿绱?,業(yè)務(wù)邏輯層的設(shè)計(jì)對(duì)于一個(gè)支持可擴(kuò)展的架構(gòu)尤為關(guān)鍵,因?yàn)樗缪萘藘蓚€(gè)不同的角色。對(duì)于數(shù)據(jù)訪問層而言,它是調(diào)用者;對(duì)于表示層而言,它卻是被調(diào)用者。依賴與被依賴的關(guān)系都糾結(jié)在業(yè)務(wù)邏輯層上,如何實(shí)現(xiàn)依賴關(guān)系的解耦,則是除了實(shí)現(xiàn)業(yè)務(wù)邏輯之外留給設(shè)計(jì)師的任務(wù)。
5.1 與領(lǐng)域?qū)<液献?/strong>
設(shè)計(jì)業(yè)務(wù)邏輯層最大的障礙不在于技術(shù),而在于對(duì)領(lǐng)域業(yè)務(wù)的分析與理解。很難想象一個(gè)不熟悉該領(lǐng)域業(yè)務(wù)規(guī)則和流程的架構(gòu)設(shè)計(jì)師能夠設(shè)計(jì)出合乎客戶需求的系統(tǒng)架構(gòu)。幾乎可以下定結(jié)論的是,業(yè)務(wù)邏輯層的設(shè)計(jì)過程必須有領(lǐng)域?qū)<业膮⑴c。在我曾經(jīng)參與開發(fā)的項(xiàng)目中,所涉及的領(lǐng)域就涵蓋了電力、半導(dǎo)體、汽車等諸多行業(yè),如果缺乏這些領(lǐng)域的專家,軟件架構(gòu)的設(shè)計(jì)尤其是業(yè)務(wù)邏輯層的設(shè)計(jì)就無從談起。這個(gè)結(jié)論唯一的例外是,架構(gòu)設(shè)計(jì)師同時(shí)又是該領(lǐng)域的專家。然而,正所謂“千軍易得,一將難求”,我們很難尋覓到這樣卓越出眾的人才。
領(lǐng)域?qū)<以趫F(tuán)隊(duì)中扮演的角色通常稱為Business Consultor(業(yè)務(wù)咨詢師),負(fù)責(zé)提供與領(lǐng)域業(yè)務(wù)有關(guān)的咨詢,與架構(gòu)師一起參與架構(gòu)與數(shù)據(jù)庫的設(shè)計(jì),撰寫需求文檔和設(shè)計(jì)用例(或者用戶故事User Story)。如果在測(cè)試階段,還應(yīng)該包括撰寫測(cè)試用例。理想的狀態(tài)是,領(lǐng)域?qū)<覒?yīng)該參與到整個(gè)項(xiàng)目的開發(fā)過程中,而不僅僅是需求階段。
領(lǐng)域?qū)<铱梢允菍iT聘請(qǐng)的對(duì)該領(lǐng)域具有較深造詣的咨詢師,也可以是作為需求提供方的客戶。在極限編程(Extreme Programming)中,就將客戶作為領(lǐng)域?qū)<乙氲秸麄€(gè)開發(fā)團(tuán)隊(duì)中。它強(qiáng)調(diào)了現(xiàn)場客戶原則?,F(xiàn)場客戶需要參與到計(jì)劃游戲、開發(fā)迭代、編碼測(cè)試等項(xiàng)目開發(fā)的各個(gè)階段。由于領(lǐng)域?qū)<遗c設(shè)計(jì)師以及開發(fā)人員組成了一個(gè)團(tuán)隊(duì),貫穿開發(fā)過程的始終,就可以避免需求理解錯(cuò)誤的情況出現(xiàn)。即使項(xiàng)目的開發(fā)與實(shí)際需求不符,也可以在項(xiàng)目早期及時(shí)修正,從而避免了項(xiàng)目不必要的延期,加強(qiáng)了對(duì)項(xiàng)目過程和成本的控制。正如Steve McConnell在構(gòu)建活動(dòng)的前期準(zhǔn)備中提及的一個(gè)原則:發(fā)現(xiàn)錯(cuò)誤的時(shí)間要盡可能接近引入該錯(cuò)誤的時(shí)間。需求的缺陷在系統(tǒng)中潛伏的時(shí)間越長,代價(jià)就越昂貴。如果在項(xiàng)目開發(fā)中能夠與領(lǐng)域?qū)<页浞值暮献?,就可以最大效果地?guī)避這樣一種惡性的鏈?zhǔn)椒磻?yīng)。
傳統(tǒng)的軟件開發(fā)模型同樣重視與領(lǐng)域?qū)<业暮献?,但這種合作主要集中在需求分析階段。例如瀑布模型,就非常強(qiáng)調(diào)早期計(jì)劃與需求調(diào)研。然而這種未雨綢繆的早期計(jì)劃方式,對(duì)架構(gòu)師與需求調(diào)研人員的技能要求非常高,它強(qiáng)調(diào)需求文檔的精確性,一旦分析出現(xiàn)偏差,或者需求發(fā)生變更,當(dāng)項(xiàng)目開發(fā)進(jìn)入設(shè)計(jì)階段后,由于缺乏與領(lǐng)域?qū)<覝贤ㄅc合作的機(jī)制,開發(fā)人員估量不到這些錯(cuò)誤與誤差,因而難以及時(shí)作出修正。一旦這些問題像毒瘤一般在系統(tǒng)中蔓延開來,逐漸暴露在開發(fā)人員面前時(shí),已經(jīng)成了一座難以逾越的高山。我們需要消耗更多的人力物力,才能夠修正這些錯(cuò)誤,從而導(dǎo)致開發(fā)成本成數(shù)量級(jí)的增加,甚至于導(dǎo)致項(xiàng)目延期。當(dāng)然還有一個(gè)好的選擇,就是放棄整個(gè)項(xiàng)目。這樣的例子不勝枚舉,事實(shí)上,項(xiàng)目開發(fā)的“滑鐵盧”,究其原因,大部分都是因?yàn)闃I(yè)務(wù)邏輯分析上出現(xiàn)了問題。
迭代式模型較之瀑布模型有很大地改進(jìn),因?yàn)樗试S變更、優(yōu)化系統(tǒng)需求,整個(gè)迭代過程實(shí)際上就是與領(lǐng)域?qū)<业暮献鬟^程,通過向客戶演示迭代所產(chǎn)生的系統(tǒng)功能,從而及時(shí)獲取反饋,并逐一解決迭代演示中出現(xiàn)的問題,保證系統(tǒng)向著合乎客戶需求的方向演化。因而,迭代式模型往往能夠解決早期計(jì)劃不足的問題,它允許在發(fā)現(xiàn)缺陷的時(shí)候,在需求變更的時(shí)候重新設(shè)計(jì)、重新編碼并重新測(cè)試。
無論采用何種開發(fā)模型,與領(lǐng)域?qū)<业暮献鞫紝⒊蔀轫?xiàng)目成敗與否的關(guān)鍵。這基于一個(gè)軟件開發(fā)的普遍真理,那就是世界上沒有不變的需求。一句經(jīng)典名言是:“沒有不變的需求,世上的軟件都改動(dòng)過3次以上,唯一一個(gè)只改動(dòng)過兩次的軟件的擁有者已經(jīng)死了,死在去修改需求的路上?!币徽Z道盡了軟件開發(fā)的殘酷與艱辛!
那么應(yīng)該如何加強(qiáng)與領(lǐng)域?qū)<业暮献髂??James Carey和Brent Carlson根據(jù)他們?cè)趨⑴c的IBM SanFrancisco項(xiàng)目中獲得的經(jīng)驗(yàn),提出了Innocent Questions模式,其意義即“改進(jìn)領(lǐng)域?qū)<液图夹g(shù)專家的溝通質(zhì)量”。在一個(gè)項(xiàng)目團(tuán)隊(duì)中,如果我們沒有一位既能擔(dān)任首席架構(gòu)師,同時(shí)又是領(lǐng)域?qū)<业娜诉x,那么加強(qiáng)領(lǐng)域?qū)<遗c技術(shù)專家的合作就顯得尤為重要了。畢竟,作為一個(gè)領(lǐng)域?qū)<叶?,可能并不熟悉軟件設(shè)計(jì)方法學(xué),也不具備面向?qū)ο箝_發(fā)和架構(gòu)設(shè)計(jì)的能力,同樣,大部分技術(shù)專家很有可能對(duì)該項(xiàng)目所涉及的業(yè)務(wù)領(lǐng)域僅停留在一知半解的地步。如果領(lǐng)域?qū)<遗c技術(shù)專家不能有效溝通,則整個(gè)項(xiàng)目的前途就岌岌可危了。
Innocent Questions模式提出的解決方案包括:
(1)選用可以與人和諧相處的人員組建開發(fā)團(tuán)隊(duì);
(2)清楚地定義角色和職權(quán);
(3)明確定義需要的交互點(diǎn);
(4)保持團(tuán)隊(duì)緊密;
(5)雇傭優(yōu)秀的人。
事實(shí)上,這已經(jīng)從技術(shù)的角度上升到對(duì)團(tuán)隊(duì)的管理層次了。就好比籃球運(yùn)動(dòng)一樣,即使你的球隊(duì)集合了五名世界上最頂尖最有天賦的球員,如果各自為戰(zhàn),要想取得比賽的勝利依舊是非常困難的。團(tuán)隊(duì)精神與權(quán)責(zé)分明才是取得勝利的保障,軟件開發(fā)同樣如此。
與領(lǐng)域?qū)<液献鞯幕A(chǔ)是保證開發(fā)團(tuán)隊(duì)中永遠(yuǎn)保留至少一名領(lǐng)域?qū)<?。他可以是系統(tǒng)的客戶,第三方公司的咨詢師,最理想是自己公司雇傭的專家。如果項(xiàng)目中缺乏這樣的一個(gè)人,那么我的建議是去雇傭他,如果你不想看到項(xiàng)目遭遇“西伯利亞寒流”的話。
確定領(lǐng)域?qū)<业慕巧蝿?wù)與職責(zé)。必須要讓團(tuán)隊(duì)中的每一個(gè)人明確領(lǐng)域?qū)<以谡麄€(gè)團(tuán)隊(duì)中究竟扮演什么樣的角色,他的職責(zé)是什么。一個(gè)合格的領(lǐng)域?qū)<冶仨殞?duì)業(yè)務(wù)領(lǐng)域有足夠深入的理解,他應(yīng)該是一個(gè)能夠俯瞰整個(gè)系統(tǒng)需求、總攬全局的人物。在項(xiàng)目開發(fā)過程中,將由他負(fù)責(zé)業(yè)務(wù)規(guī)則和流程的制定,負(fù)責(zé)與客戶的溝通,需求的調(diào)研與討論,并于設(shè)計(jì)師一起參與系統(tǒng)架構(gòu)的設(shè)計(jì)。編檔是領(lǐng)域?qū)<冶仨殔⑴c的工作,無論是需求文檔還是設(shè)計(jì)文檔,以及用例的編寫,領(lǐng)域?qū)<一蛘咛岢鲆庖?,或者作為撰寫的作者,至少他也?yīng)該是評(píng)審委員會(huì)的重要成員。
規(guī)范業(yè)務(wù)領(lǐng)域的術(shù)語和技術(shù)術(shù)語。領(lǐng)域?qū)<液图夹g(shù)專家必須在保證不產(chǎn)生二義性的語義環(huán)境下進(jìn)行溝通與交流。如果出現(xiàn)理解上的分歧,我們必須及時(shí)解決,通過討論確立術(shù)語標(biāo)準(zhǔn)。很難想象兩個(gè)語言不通的人能夠相互合作愉快,解決的辦法是加入一位翻譯人員。在領(lǐng)域?qū)<遗c技術(shù)專家之間搭建一座語義上的橋梁,使其能夠相互理解、相互認(rèn)同。還有一個(gè)辦法是在團(tuán)隊(duì)內(nèi)部開展培訓(xùn)活動(dòng)。尤其對(duì)于開發(fā)人員而言,或多或少地了解一些業(yè)務(wù)領(lǐng)域知識(shí),對(duì)于項(xiàng)目的開發(fā)有很大的幫助。在我參與過的半導(dǎo)體領(lǐng)域的項(xiàng)目開發(fā),團(tuán)隊(duì)就專門邀請(qǐng)了半導(dǎo)體行業(yè)的專家就生產(chǎn)過程的業(yè)務(wù)邏輯進(jìn)行了全方位的介紹與培訓(xùn)。正所謂“磨刀不誤砍柴工”,雖然我們消費(fèi)了培訓(xùn)的時(shí)間,但對(duì)于掌握了業(yè)務(wù)規(guī)則與流程的開發(fā)人員,卻能夠提升項(xiàng)目開發(fā)進(jìn)度,總體上節(jié)約了開發(fā)成本。
加強(qiáng)與客戶的溝通??蛻敉瑫r(shí)也可以作為團(tuán)隊(duì)的領(lǐng)域?qū)<?,極限編程的現(xiàn)場客戶原則是最好的示例。但現(xiàn)實(shí)并不都如此的完美,在無法要求客戶成為開發(fā)團(tuán)隊(duì)中的固定一員時(shí),聘請(qǐng)或者安排一個(gè)專門的領(lǐng)域?qū)<遥訌?qiáng)與客戶的溝通,就顯得尤為重要。項(xiàng)目可以通過領(lǐng)域?qū)<耀@得客戶的及時(shí)反饋。而通過領(lǐng)域?qū)<胰チ私庾兏说男枨?,?huì)在最大程度上減少需求誤差的可能。
5.2 業(yè)務(wù)邏輯層的模式應(yīng)用
Martin Fowler在《企業(yè)應(yīng)用架構(gòu)模式》一書中對(duì)領(lǐng)域?qū)樱礃I(yè)務(wù)邏輯層)的架構(gòu)模式作了整體概括,他將業(yè)務(wù)邏輯設(shè)計(jì)分為三種主要的模式:Transaction Script、Domain Model和Table Module。
Transaction Script模式將業(yè)務(wù)邏輯看作是一個(gè)個(gè)過程,是比較典型的面向過程開發(fā)模式。應(yīng)用Transaction Script模式可以不需要數(shù)據(jù)訪問層,而是利用SQL語句直接訪問數(shù)據(jù)庫。為了有效地管理SQL語句,可以將與數(shù)據(jù)庫訪問有關(guān)的行為放到一個(gè)專門的Gateway類中。應(yīng)用Transaction Script模式不需要太多面向?qū)ο笾R(shí),簡單直接的特性是該模式全部價(jià)值之所在。因而,在許多業(yè)務(wù)邏輯相對(duì)簡單的項(xiàng)目中,應(yīng)用Transaction Script模式較多。
Domain Model模式是典型的面向?qū)ο笤O(shè)計(jì)思想的體現(xiàn)。它充分考慮了業(yè)務(wù)邏輯的復(fù)雜多變,引入了Strategy模式等設(shè)計(jì)模式思想,并通過建立領(lǐng)域?qū)ο笠约俺橄蠼涌?,?shí)現(xiàn)模式的可擴(kuò)展性,并利用面向?qū)ο笏枷肱c身俱來的特性,如繼承、封裝與多態(tài),用于處理復(fù)雜多變的業(yè)務(wù)邏輯。唯一制約該模式應(yīng)用的是對(duì)象與關(guān)系數(shù)據(jù)庫的映射。我們可以引入ORM工具,或者利用Data Mapper模式來完成關(guān)系向?qū)ο蟮挠成洹?/p>
與Domain Model模式相似的是Table Module模式,它同樣具有面向?qū)ο笤O(shè)計(jì)的思想,唯一不同的是它獲得的對(duì)象并非是單純的領(lǐng)域?qū)ο螅荄ataSet對(duì)象。如果為關(guān)系數(shù)據(jù)表與對(duì)象建立一個(gè)簡單的映射關(guān)系,那么Domain Model模式就是為數(shù)據(jù)表中的每一條記錄建立一個(gè)領(lǐng)域?qū)ο螅鳷able Module模式則是將整個(gè)數(shù)據(jù)表看作是一個(gè)完整的對(duì)象。雖然利用DataSet對(duì)象會(huì)丟失面向?qū)ο蟮幕咎匦?,但它在為表示層提供?shù)據(jù)源支持方面卻有著得天獨(dú)厚的優(yōu)勢(shì)。尤其是在.Net平臺(tái)下,ADO.NET與Web控件都為Table Module模式提供了生長的肥沃土壤。
5.3 PetShop的業(yè)務(wù)邏輯層設(shè)計(jì)
PetShop在業(yè)務(wù)邏輯層設(shè)計(jì)中引入了Domain Model模式,這與數(shù)據(jù)訪問層對(duì)于數(shù)據(jù)對(duì)象的支持是分不開的。由于PetShop并沒有對(duì)寵物網(wǎng)上商店的業(yè)務(wù)邏輯進(jìn)行深入,也省略了許多復(fù)雜細(xì)節(jié)的商務(wù)邏輯,因而在Domain Model模式的應(yīng)用上并不明顯。最典型地應(yīng)該是對(duì)Order領(lǐng)域?qū)ο蟮奶幚矸绞?,通過引入Strategy模式完成對(duì)插入訂單行為的封裝。關(guān)于這一點(diǎn),我已在第27章有了詳盡的描述,這里就不再贅述。
本應(yīng)是系統(tǒng)架構(gòu)設(shè)計(jì)中最核心的業(yè)務(wù)邏輯層,由于簡化了業(yè)務(wù)流程的緣故,使得PetShop在這一層的設(shè)計(jì)有些乏善可陳。雖然在業(yè)務(wù)邏輯層中,針對(duì)B2C業(yè)務(wù)定義了相關(guān)的領(lǐng)域?qū)ο?,但這些領(lǐng)域?qū)ο髢H僅是完成了對(duì)數(shù)據(jù)訪問層中數(shù)據(jù)對(duì)象的簡單封裝而已,其目的僅在于分離層次,以支持對(duì)各種數(shù)據(jù)庫的擴(kuò)展,同時(shí)將SQL語句排除在業(yè)務(wù)邏輯層外,避免了SQL語句的四處蔓延。
最能體現(xiàn)PetShop業(yè)務(wù)邏輯的除了對(duì)訂單的管理之外,還包括購物車(Shopping Cart)與Wish List的管理。在PetShop的BLL模塊中,定義了Cart類來負(fù)責(zé)相關(guān)的業(yè)務(wù)邏輯,定義如下:
[Serializable]
public class Cart
{
private Dictionary cartItems = new Dictionary();
public decimal Total
{
get
{
decimal total = 0;
foreach (CartItemInfo item in cartItems.Values)
total += item.Price * item.Quantity;
return total;
}
}
public void SetQuantity(string itemId, int qty)
{
cartItems[itemId].Quantity = qty;
}
public int Count
{
get { return cartItems.Count; }
}
public void Add(string itemId)
{
CartItemInfo cartItem;
if (!cartItems.TryGetValue(itemId, out cartItem))
{
Item item = new Item();
ItemInfo data = item.GetItem(itemId);
if (data != null)
{
CartItemInfo newItem = new CartItemInfo(itemId, data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId, data.ProductId);
cartItems.Add(itemId, newItem);
}
}
else
cartItem.Quantity++;
}
//其他方法略;
}
Cart類通過一個(gè)Dictionary對(duì)象來負(fù)責(zé)對(duì)購物車內(nèi)容的存儲(chǔ),同時(shí)定義了Add、Remove、Clear等方法,來實(shí)現(xiàn)對(duì)購物車內(nèi)容的管理。
在前面我提到PetShop業(yè)務(wù)邏輯層中的領(lǐng)域?qū)ο髢H僅是完成對(duì)數(shù)據(jù)對(duì)象的簡單封裝,但這種分離層次的方法在架構(gòu)設(shè)計(jì)中依然扮演了舉足輕重的作用。以Cart類的Add()方法為例,在方法內(nèi)部引入了PetShop.BLL.Item領(lǐng)域?qū)ο?,并調(diào)用了Item對(duì)象的GetItem()方法。如果沒有在業(yè)務(wù)邏輯層封裝Item對(duì)象,而是直接調(diào)用數(shù)據(jù)訪問層的Item數(shù)據(jù)對(duì)象,為保證層次間的弱依賴關(guān)系,就需要調(diào)用工廠對(duì)象的工廠方法來創(chuàng)建PetShop.IDAL.IItem接口類型對(duì)象。一旦數(shù)據(jù)訪問層的Item對(duì)象被多次調(diào)用,就會(huì)造成重復(fù)代碼,既不離于程序的修改與擴(kuò)展,也導(dǎo)致程序結(jié)構(gòu)生長為臃腫的態(tài)勢(shì)。
此外,領(lǐng)域?qū)ο髮?duì)數(shù)據(jù)訪問層數(shù)據(jù)對(duì)象的封裝,也有利于表示層對(duì)業(yè)務(wù)邏輯層的調(diào)用。在三層式架構(gòu)中,表示層應(yīng)該是對(duì)于數(shù)據(jù)訪問層是“無知”的,這樣既減少了層與層間的依賴關(guān)系,也能有效避免“循環(huán)依賴”的后果。
值得商榷的是Cart類的Total屬性。其值的獲取是通過遍歷購物車集合,然后累加價(jià)格與商品數(shù)量的乘積。這里顯然簡化了業(yè)務(wù)邏輯,而沒有充分考慮需求的擴(kuò)展。事實(shí)上,這種獲取購物車總價(jià)格的算法,在大多數(shù)情況下僅僅是其中的一種策略而已,我們還應(yīng)該考慮折扣的情況。例如,當(dāng)總價(jià)格超過100元時(shí),可以給與顧客一定的折扣,這是與網(wǎng)站的促銷計(jì)劃相關(guān)的。除了給與折扣的促銷計(jì)劃外,網(wǎng)站也可以考慮贈(zèng)送禮品的促銷策略,因此我們有必要引入Strategy模式,定義接口IOnSaleStrategy:
public interface IOnSaleStrategy
{
decimal CalculateTotalPrice(Dictionary cartItems);
}
如此一來,我們可以為Cart類定義一個(gè)有參數(shù)的構(gòu)造函數(shù):
private IOnSaleStrategy m_onSale;
public Cart(IOnSaleStrategy onSale)
{
m_onSale = onSale;
}
那么Total屬性就可以修改為:
public decimal Total
{
get {return m_onSale.CalculateTotalPrice(cartItems);}
}
如此一來,就可以使得Cart類能夠有效地支持網(wǎng)站推出的促銷計(jì)劃,也符合開-閉原則。同樣的,這種設(shè)計(jì)方式也是Domain Model模式的體現(xiàn)。修改后的設(shè)計(jì)如圖5-1所示:
圖5-1 引入Strategy模式
作為一個(gè)B2C的電子商務(wù)架構(gòu),它所涉及的業(yè)務(wù)領(lǐng)域已為大部分設(shè)計(jì)師與開發(fā)人員所熟悉,因而在本例中,與領(lǐng)域?qū)<业暮献黠@得并不那么重要。然而,如果我們要開發(fā)一個(gè)成功的電子商務(wù)網(wǎng)站,與領(lǐng)域?qū)<业暮献魅匀皇潜夭豢缮俚?。以訂單的管理而言,如果考慮復(fù)雜的商業(yè)應(yīng)用,就需要管理訂單的跟蹤(Tracking),與網(wǎng)上銀行的合作,賬戶安全性,庫存管理,物流管理,以及客戶關(guān)系管理(CRM)。整個(gè)業(yè)務(wù)過程卻涵蓋了諸如電子商務(wù)、銀行、物流、客戶關(guān)系學(xué)等諸多領(lǐng)域,如果沒有領(lǐng)域?qū)<业膮⑴c,業(yè)務(wù)邏輯層的設(shè)計(jì)也許會(huì)“敗走麥城”。
5.4 與數(shù)據(jù)訪問層的通信
業(yè)務(wù)邏輯層需要與數(shù)據(jù)訪問層通信,利用數(shù)據(jù)訪問層訪問數(shù)據(jù)庫,因此業(yè)務(wù)邏輯層與數(shù)據(jù)訪問層之間就存在依賴關(guān)系。在數(shù)據(jù)訪問層引入接口程序集以及數(shù)據(jù)工廠的設(shè)計(jì)前提下,能夠做到兩者間關(guān)系為弱依賴。我們從業(yè)務(wù)邏輯層的引用程序集中可以看到,BLL模塊并沒有引用SQLServerDAL和OracleDAL程序集。在業(yè)務(wù)邏輯層中,有關(guān)數(shù)據(jù)訪問層中數(shù)據(jù)對(duì)象的調(diào)用,均利用多態(tài)原理定義了抽象的接口類型對(duì)象,然后利用工廠對(duì)象的工廠方法創(chuàng)建具體的數(shù)據(jù)對(duì)象。如PetShop.BLL.PetShop領(lǐng)域?qū)ο笏荆?/p>
namespace PetShop.BLL
{
public class Product
{
//根據(jù)工廠對(duì)象創(chuàng)建IProduct接口類型實(shí)例;
private static readonly IProduct dal = PetShop.DALFactory.DataAccess.CreateProduct();
//調(diào)用IProduct對(duì)象的接口方法GetProductByCategory();
public IList
GetProductsByCategory(string category)
{
// 如果為空則新建List對(duì)象;
if(string.IsNullOrEmpty(category))
return new List ();
// 通過數(shù)據(jù)訪問層的數(shù)據(jù)對(duì)象訪問數(shù)據(jù)庫;
return dal.GetProductsByCategory(category);
}
//其他方法略;
}
}
在領(lǐng)域?qū)ο驪roduct類中,利用數(shù)據(jù)訪問層的工廠類DALFactory.DataAccess創(chuàng)建PetShop.IDAL.IProduct類型的實(shí)例,如此就可以解除對(duì)具體程序集SQLServerDAL或OracleDAL的依賴。只要PetShop.IDAL的接口方法不變,即使修改了IDAL接口模塊的具體實(shí)現(xiàn),都不會(huì)影響業(yè)務(wù)邏輯層的實(shí)現(xiàn)。這種松散的弱耦合關(guān)系,才能夠最大程度地支持架構(gòu)的可擴(kuò)展。
領(lǐng)域?qū)ο驪roduct實(shí)際上還完成了對(duì)數(shù)據(jù)對(duì)象Product的封裝,它們暴露在外的接口方法是一致地,正是通過封裝,使得表示層可以完全脫離數(shù)據(jù)庫以及數(shù)據(jù)訪問層,表示層的調(diào)用者僅需要關(guān)注業(yè)務(wù)邏輯層的實(shí)現(xiàn)邏輯,以及領(lǐng)域?qū)ο蟊┞兜慕涌诤驼{(diào)用方式。事實(shí)上,只要設(shè)計(jì)合理,規(guī)范了各個(gè)層次的接口方法,三層式架構(gòu)的設(shè)計(jì)完全可以分離開由不同的開發(fā)人員同時(shí)開發(fā),這就可以有效地利用開發(fā)資源,縮短項(xiàng)目開發(fā)周期。
5.5 面向接口設(shè)計(jì)
也許是業(yè)務(wù)邏輯比較簡單地緣故,在業(yè)務(wù)邏輯層的設(shè)計(jì)中,并沒有秉承在數(shù)據(jù)訪問層中面向接口設(shè)計(jì)的思想。除了完成對(duì)插入訂單策略的抽象外,整個(gè)業(yè)務(wù)邏輯層僅以BLL模塊實(shí)現(xiàn),沒有為領(lǐng)域?qū)ο蠖x抽象的接口。因而PetShop的表示層與業(yè)務(wù)邏輯層就存在強(qiáng)依賴關(guān)系,如果業(yè)務(wù)邏輯層中的需求發(fā)生變更,就必然會(huì)影響表示層的實(shí)現(xiàn)。唯一可堪欣慰的是,由于我們采用分層式架構(gòu)將用戶界面與業(yè)務(wù)領(lǐng)域邏輯完全分離,一旦用戶界面發(fā)生更改,例如將B/S架構(gòu)修改為C/S架構(gòu),那么業(yè)務(wù)邏輯層的實(shí)現(xiàn)模塊是可以完全重用的。
然而,最理想的方式仍然是面向接口設(shè)計(jì)。根據(jù)第28章對(duì)ASP.NET緩存的分析,我們可以將表示層App_Code下的Proxy類與Utility類劃分到業(yè)務(wù)邏輯層中,并修改這些靜態(tài)類為實(shí)例類,并將這些類中與業(yè)務(wù)領(lǐng)域有關(guān)的方法抽象為接口,然后建立如數(shù)據(jù)訪問層一樣的抽象工廠。通過“依賴注入”方式,解除與具體領(lǐng)域?qū)ο箢惖囊蕾嚕沟帽硎緦觾H依賴于業(yè)務(wù)邏輯層的接口程序集以及工廠模塊。
那么,這樣的設(shè)計(jì)是否有“過度設(shè)計(jì)”的嫌疑呢?我們需要依據(jù)業(yè)務(wù)邏輯的需求情況而定。此外,如果我們需要引入緩存機(jī)制,為領(lǐng)域?qū)ο髣?chuàng)建代理類,那么為領(lǐng)域?qū)ο蠼⒔涌?,就顯得尤為必要。我們可以建立一個(gè)專門的接口模塊IBLL,用以定義領(lǐng)域?qū)ο蟮慕涌?。以Product領(lǐng)域?qū)ο鬄槔?,我們可以建立IProduct接口:
public interface IProduct
{
IList GetProductByCategory(string category);
IList GetProductByCategory(string[] keywords);
ProductInfo GetProduct(string productId);
}
在BLL模塊中可以引入對(duì)IBLL程序集的依賴,則領(lǐng)域?qū)ο驪roduct的定義如下:
public class Product:IProduct
{
public IList GetProductByCategory(string category) { //實(shí)現(xiàn)略; }
public IList GetProductByCategory(string[] keywords) { //實(shí)現(xiàn)略; }
public ProductInfo GetProduct(string productId) { //實(shí)現(xiàn)略; }
}
然后我們可以為代理對(duì)象建立專門的程序集BLLProxy,它不僅引入對(duì)IBLL程序集的依賴,同時(shí)還將依賴于BLL程序集。此時(shí)代理對(duì)象ProductDataProxy的定義如下:
using PetShop.IBLL;
using PetShop.BLL;
namespace PetShop.BLLProxy
{
public class ProductDataProxy:IProduct
{
public IList GetProductByCategory(string category)
{
Product product = new Product();
//其他實(shí)現(xiàn)略;
}
public IList GetProductByCategory(string[] keywords) { //實(shí)現(xiàn)略; }
public ProductInfo GetProduct(string productId) { //實(shí)現(xiàn)略; }
}
}
如此的設(shè)計(jì)正是典型的Proxy模式,其類結(jié)構(gòu)如圖5-2所示:
圖5-2 Proxy模式
參照數(shù)據(jù)訪問層的設(shè)計(jì)方法,我們可以為領(lǐng)域?qū)ο蠹按韺?duì)象建立抽象工廠,并在web.config中配置相關(guān)的配置節(jié),然后利用反射技術(shù)創(chuàng)建具體的對(duì)象實(shí)例。如此一來,表示層就可以僅僅依賴PetShop.IBLL程序集以及工廠模塊,如此就可以解除表示層與具體領(lǐng)域?qū)ο笾g的依賴關(guān)系。表示層與修改后的業(yè)務(wù)邏輯層的關(guān)系如圖5-3所示:
圖5-3 修改后的業(yè)務(wù)邏輯層與表示層的關(guān)系
圖5-4則是PetShop 4.0原有設(shè)計(jì)的層次關(guān)系圖:
圖5-4 PetShop 4.0中表示層與業(yè)務(wù)邏輯層的關(guān)系
通過比較圖5-3與圖5-4,雖然后者不管是模塊的個(gè)數(shù),還是模塊之間的關(guān)系,都相對(duì)更加簡單,然而Web Component組件與業(yè)務(wù)邏輯層之間卻是強(qiáng)耦合的,這樣的設(shè)計(jì)不利于應(yīng)對(duì)業(yè)務(wù)擴(kuò)展與需求變更。通過引入接口模塊IBLL與工廠模塊BLLFactory,解除了與具體模塊BLL的依賴關(guān)系。這種設(shè)計(jì)對(duì)于業(yè)務(wù)邏輯相對(duì)比較復(fù)雜的系統(tǒng)而言,更符合面向?qū)ο蟮脑O(shè)計(jì)思想,有利于我們建立可抽取、可替換的“抽屜”式三層架構(gòu)。
以上就是PetShop的業(yè)務(wù)邏輯層設(shè)計(jì)全部內(nèi)容,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
您可能感興趣的文章:- 學(xué)會(huì)sql數(shù)據(jù)庫關(guān)系圖(Petshop)
- 《解剖PetShop》之一:PetShop的系統(tǒng)架構(gòu)設(shè)計(jì)
- 《解剖PetShop》之二:PetShop數(shù)據(jù)訪問層數(shù)之據(jù)庫訪問設(shè)計(jì)
- 《解剖PetShop》之三:PetShop數(shù)據(jù)訪問層之消息處理
- 《解剖PetShop》之四:PetShop之ASP.NET緩存
- 《解剖PetShop》之六:PetShop之表示層設(shè)計(jì)