原文地址:http://www.cnblogs.com/1-2-3/archive/2008/08/04/model-method-part1.html
原文作者:景春雷
??????????????????? 一錯再錯的這故事才精彩
??????????????????????????????????????——樸樹 《我愛你再見》
摘要
????? 即使讀了再多的書、跟過再多的項目,到了需要自己創建領域模型的時候,還是感覺不知從哪兒下手。就像即使看過再多的小說,到了自己想寫小說的時候,仍會感 覺無從下筆……本文將給出3個實用的建模心法,并通過一個實際項目介紹如何應用USM三視圖法邁出建模第一步。
殷六俠即將平生第一次下山做任務
殷六俠來到張三豐的禪房。
殷六俠:“師傅,弟子就要下山去了,特來向師傅告別。”
張三豐:“梨亭啊,雖然你平時讀書很用功,也跟著師兄們做了一些項目,但這次畢竟是第一次獨力建模,心里有沒有譜呀?”
殷六俠:“說實話,弟子現在大腦一片空白。”
張三豐:“……”
殷六俠:“弟子雖然已經把《領域驅動開發心經》、《設計模式真經》、《分析模式真經》讀了個滾瓜爛熟,可是到了用的時候,還是有一種無處著力的感覺。”
張三豐:“簡直都不知道從哪開始對不對?這是很正常的。這次事出緊急,只能由你一個人立即趕往同仁堂,實在有些難為你了。不過好在現在科技發達了,有什么問題可以隨時用QQ與為師聯系。時候不早了,你快些動身吧。”
殷六俠:“那徒兒就告退了,師傅保重!”
張三豐:“等一等,你進禪房之前可曾聽到為師鼓瑟?”
殷六俠:“師傅是否想說建模就如同鼓瑟,音樂的好聽與否并不因為單獨的某個音符的高低短長,而是所有音符連續起來的效果?”
張三豐:“好,很好。我的七個徒弟里面,除了你五師哥,就是你悟性最高了。快快下山去吧。”
殷六俠:“徒兒告退。”
第一天的QQ聊天記錄
殷六俠:師傅,弟子已經順利到達同仁堂,這邊的領域專家用一上午的時間向我介紹了一下住院部的業務,我整理出了十幾個用例:(限于篇幅,這里僅列出3個)
????? 用例(use case) 由用戶的目標聯系在一起的一組場景。
??????場景(scenario) 一系列表述用戶和系統之間一次交互的步驟。
名詞法為什么行不通
張三豐:很好,用例做得挺不錯的,接下來你打算做什么?
殷六俠:謝謝師傅夸獎,做用例其實并不難,只要把領域專家說的東西稍加整理就行了。弟子接下來就準備根據用例制作領域 模型了。我想用名詞法,首先找到用例中的名詞,例如“收款員”就是實體,與它相關的動詞,例如“提交入院登記”就是實體的一個方法。然后我再根據面向對象 設計的原則和設計模式對類和職責進行調整。
張三豐:很遺憾,我不得不指出你犯了一個初學者常犯的錯誤——把用例中的名詞等同于領域模型里的實體。事實上,用例代表的是系統的外觀,用例中的名詞和系統中的實體沒有任何聯系。
殷六俠:可是,領域模型里難道不該有一個叫“收款員”的實體么?
張三豐:沒錯,領域模型里會有一個叫“收款員”的實體,不過它和用例里面的收款員可不是一回事。用例里面的收款員處于系統外部,是一個活生生的人;而領域模型里的“收款員”實體,確切的說應該叫“收款員基本信息”,這個實體只知道收款員的姓名、性別、年齡和權限等信息。
殷六俠:我注意到您用了“知道”這個詞,您是不是想說“收款員基本信息”實體知道得太少而無法承擔“提交入院登記”這個職責呢?
張三豐:記得《蜘蛛俠》里面的經典臺詞吧?“能力越大,責任就越大。”對于類來說,知道的越多,操作就越多。一個類有 2種職責:1)知識性職責,包括屬性、關聯和無副作用的方法;2)操作性職責,就是指類的含副作用的方法。一個原則就是,要把職責分配給最容易取得它所需 的信息的那個類。
殷六俠:可是,建模的時候會面臨著3個問題:“模型里要有哪些類?類之間如何關聯?類有哪些職責?”應該首先考慮哪個問題呢?或者說哪個問題比較重要呢?
張三豐:記得有句古話叫“程序=數據結構+算法”吧?那么你說是先有數據結構呢?還是先有算法?
殷六俠:若要為解決某個問題設計一個算法,雖然有可能會先考慮數據結構或算法,但是其實它們兩個是相互配合、無法單獨工作的吧?也就是說,它們在理論上應該是同時產生、同等重要的吧?
張三豐:沒錯。而類是把數據結構和算法捏到了一起,所以理論上這三個問題也是被同時解決的。
殷六俠:可是恕徒兒愚笨,要同時思考這三個問題我可實在是辦不到。
張三豐:好在實體類還有一個更重要的職責:它要具有延續性和生命周期,并且以identity而不是其它的屬性來相互區別。我們要首先按這個職責來構建實體和關聯,然后再考慮實體的其它職責,這樣就簡單多了。
殷六俠:那么設計模式是否對找出實體有所幫助呢?
張三豐:應該說用處不大。因為建模的目的是構建一個領域模型來仿真現實的業務,它的結構恰巧與設計模式里的類結構相同 的情況并不多見。需要注意的是,設計模式關注的主要是如何應用OO的技術手段(接口和多態)來簡化設計和增加彈性,它們的關注點是不同的。現實業務里組合 的情況很常見,例如合同包含一些產品,產品由部件組成,但是卻不一定需要使用Composite模式那樣的類結構,因為可能并不需要Composite模 式所提供的那么強大的一致性和彈性。如果勉強使用Composite模式反而會使模型難以理解,還會由于使用了過窄的接口導致大量的向下轉型操作,為 Client代碼增加了不必要的復雜性。設計模式確實提供了誘人的一致性和高內聚性,但是你得首先找到領域中的一致性才行。
殷六俠:那我該如何找出實體類呢?實體實體,就是實際存在的物體吧?我注意到每個患者床上都掛著一個床頭卡,那么模型里應該有一個床頭卡實體吧?還有我可以去收集所有的報表,然后從這些報表里的字段來分析出該有哪些實體。
張三豐:我希望你從一開始就有一個清醒的認識:建模是一項無中生有的、100%的創造性工作,并不存在某種方法或公式 可以讓你從用例或實際物體里推導出領域模型。領域模型是“分析”不出來的,它是被“設計”出來的。所以,領域模型里會有一些現實世界里并不存在的實體,當 然也會有與現實世界里的物體同名的實體,但是它只是表現現實物體的某個方面,所以它的職責也就很可能與現實物體不同。至于報表,一般多是取自幾個實體中的 數據,還要進行匯總等統計操作,所以比較適合用來驗證模型,而不是一開的創建模型的工作。
殷六俠:唉,師傅,您越說我就越糊涂,恐怕弟子是要辜負師傅的重托了……
張三豐:別急,其實建模還是有一些實用技巧的,待為師傳你建模心法。呃,今天時候不早了,明天再說吧。拜拜
殷六俠:師傅晚安。
殷六俠回到客棧,周圍突然一下子變黑了,屏幕上出現一行小字:“正在存盤……”
第二天的QQ聊天記錄
殷六俠:師傅早。
張三豐:早。昨天說到哪了?對,建模心法。先傳你建模心法1。
建模心法1 尋找線索實體。
殷六俠:什么叫線索實體?
張三豐:就是生命周期恰好貫穿整個業務流程的那個實體。這個實體就像一條線,將整個業務流程中的其它實體串起來,形成星型的結構。
殷六俠:我明白了,例如一個企業的銷售業務就是簽訂合同、執行合同,那么“合同”就是這樣的一個線索實體。
張三豐:沒錯。
殷六俠:讓我想想,對于住院管理來說,這個線索實體的生命周期應該在患者入院時開始,患者出院時結束。“病歷本”符合這一條件,不過讓其它的實體都關聯“病歷本”似乎不大自然。由于患者可能多次住院,所以患者這個實體的生命周期顯然過長而不適合作為線索實體。
張三豐:很好。
殷六俠:這個線索實體可以定義為“患者的一次住院”。嗯……可以叫“住院履歷”,或者干脆叫“住院記錄”好了。
張三豐:這個名字還算湊合吧。起一個好名字還是很難的,以后有空多去武當山下的釀名齋坐坐。
殷六俠:是,師傅。有了這個線索實體類,其它的幾個相關的實體也很自然地產生了。
張三豐:看上去挺不錯的,不過我要提醒你,領域模型是要能滿足所有用例的所有場景的,這個模型里沒有包括費用相關的實體呀。
殷六俠:是啊,直覺上費用的處理挺復雜的。
張三豐:你的直覺很正確。現在為師傳你建模心法2。
建模心法2 尋找相似場景。
張三豐:如果幾個用例中都包含相似的場景,例如“計費”,就可以把這些相似的場景抽取出來成為一個單獨的用例,再讓其 它用例包含(include)這個被抽取出來的用例,不過這不是必須的。最重要的是你要認識到尋找相似場景的意義。越多的用例包含這個相似場景,就說明這 個場景的業務越復雜,設計不當的可能性越高;將來重構的成本也越大。換句話說就是風險越發的高,所以更需要你加倍仔細、小心地處理。
殷六俠:您是說我們要設計一個實體-關系結構,可以滿足所有的相似場景?感覺好難的說。
張三豐:沒錯,有時會感覺太復雜而無法把握,這時可以試試建模心法3。
建模心法3 使用示例場景(Sample Scenario),尋找一致性。
張三豐:示例場景是一組(最好是連續的)模擬真實業務的場景。例如和費用相關的示例場景為:
場景一:患者入院登記。交預繳金200元。申請獲得500元擔保金。為醫保患者,醫保卡內有1000元。
場景二:第一天消費感冒通一盒(50元)、抽血一次(30元)、床位費(100元)
場景三:經申請,擔保金額度升為1000元。另,護士重新讀取了醫保卡,醫保卡里的余額是2000元。
場景三:鑲金牙(1000元,走現金帳戶),擔保金+賬戶余額=100元小于最低預繳金額(200元,系統參數),要求續費
場景四:續交現金預繳金2000元。
場景五:護士刷醫保卡繳費。
????? 最好與領域專家共同制作示例場景。要涵蓋所有可能的情況,同時注意不要包含現實業務中不存在的情況以避免不必要的復雜性。如果領域專家指出某種情況是不存 在的,應該進一步追問其原因。原因可能是“目前為止還從未出現過這種情況,雖然理論上是合理的”,“這種情況是違反法律或行規的”,“這么做將傷害公司或 客戶的利益”,“為了方便某個部門或某類員工的工作”、“也許這么做會更合算,但是我們領導偏偏就要求要按現在的做法去做”等等。無論是何種原因,都可能 成為未來的變更點。雖然目前的設計不必理會這些不可能情況,但是可以思考一下將來發生變更時如何修改現有設計,以此為契機很可能會發現更為簡單且富有彈性 的設計。
????? 上面的示例場景是不完全的,因為沒有涉及退費相關的場景(抱歉限于篇幅沒有給出退費相關的用例),就以上面的5個示例場景,該如何設計領域模型呢?
????? 可以注意到示例場景中存在兩類金額:預繳金和擔保金。執行某醫囑時可能從預繳金中扣錢,但是也可能從醫保卡中扣錢。出于需要為患者打印每日費用明細(抱歉限于篇幅沒有給出這個用例)和退費,需要記錄每筆費用,這讓你想到了什么?
殷六俠:這讓我想到可以試試賬戶+變更記錄這個模式(關于常用的分析模式,我打算以后再寫幾篇來專門介紹),讓我仔細想想……
1小時之后
殷六俠:我想到醫保卡余額與擔保金在概念上很相似,這樣就可以一致地處理醫保卡賬戶和現金賬戶了。
殷六俠:領域模型變成這樣:
殷六俠:示例場景確實給了我不少感性認識。
張三豐:隨著OOA&D的流行,人們普遍認同一開始建模的時候不應考慮實現細節,而應把注意力集中在對領域的 深刻理解上。領域模型里的對象結構與實際的數據庫表結構可以有很大的不同;實體間的關聯也不一定非得實現為外鍵關聯……不過請不要把可以忽略實現細節等同 于可以忽略細節領域知識。正所謂“細節就是魔鬼”,醉心于抽象和一致性,而沒有把同等甚至更多的精力投入到發現不一致的細微之處上,實在是一種很危險的做 法。所謂“具而不抽則罔,抽而不具則殆”,讓思維在抽象和具體之間來回移動才更有效。示例場景還給了你一個“橫切”用例的機會,可以讓你對業務有一個更加 立體的認識。
殷六俠:制作示例場景還是一個發現遺漏用例和提出尖銳問題的好機會。例如剛剛我就很自然地想到“擔保金只是對現金賬戶 來說的么?還是對現金賬戶和醫保賬戶同時有效?例如當現金賬戶和醫保卡里面的錢都用光了,而擔保金為3000元時,這時計費只能走現金賬戶還是也可以走醫 保賬戶呢?”
小結 USM三視圖法
????? 運用本文介紹的USM(用例-示例場景-領域模型)三視圖法,可令 Modeler 從不同視角觀察企業業務,讓思維在抽象和具體之間來回移動,互相促進、完善,發現未曾注意的細節,找出概念上的一致性,不斷加深對領域的理解。希望USM 三視圖法成為虛空中的一塊踏腳石,幫助 Modeler 邁出建模第一步。
?

參考文獻
Craig Larman,UML和模式應用。機械工業出版社,2004.
Martin Fowler 著,徐家福 譯,UML 精粹(第2版)標準對象建模語言簡明指南。清華大學出版社,2002。
Eric Evans, 領域驅動設計(影印版)。人民郵電出版社,2007。
Martin Fowler, 分析模式(影印版)。中國電力出版社,2003.
RicCC,分析模式讀書筆記。博客園,2008.