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