貧血模型與充血模型的對比
上週翻譯了MartinFowler的貧血模型,當即就答應溫老師了溫謙老師提出能不能寫個示例來解釋一下貧血模型的要求。在網上也查了一些資料,本想轉載一篇,轉念一想,算了吧,按自己的理解去寫吧。不論對與錯,為大家提供一個靶子,同意也好,反對也罷,希望大家也把自己的見解記錄在這裡。
在這裡再次宣告下,本人功力尚淺,難免有理解不到位的地方,所以還請大家在閱讀的時候發現並指正。
打算藉助事物指令碼和領域模型兩種方式來揭開貧血與充血之間的異同。
我們先了解一下事物指令碼和領域模型的概念。
事物指令碼: 事務指令碼的核心是過程,通過過程的呼叫來組織業務邏輯,每個過程處理來自表現層的單個請求。大部分業務應用都可以被看成一系列事務,從某種程度上來說,通過事務指令碼處理業務,就像執行一條條Sql語句來實現資料庫資訊的處理。事務指令碼把業務邏輯組織成單個過程,在過程中直接呼叫資料庫,業務邏輯在服務(Service)層處理。
領域模型: 領域模型的特點也比較明顯, 屬於物件導向設計,領域模型具備自己的屬性行為狀態,並與現實世界的業務物件相對映。各類具備明確的職責劃分,領域物件元素之間通過聚合和引用等關係配合解決實際業務應用和規則。可複用,可維護,易擴充套件,可以採用合適的設計模型進行詳細設計。缺點是相對複雜,要求設計人員有良好的抽象能力。
大家很奇怪吧,標題不是“貧血模型與充血模型的對比”嗎,怎麼扯到事物指令碼和領域模型上了呢。我是這樣理解的:在面向事物指令碼程式設計中所使用的物件就是我們今天的主角之一“貧血模型”,當然領域模型自然也就對應著“充血模型”。那麼他們最大的區別在哪裡呢?其實Marth Folwer的貧血模型一文中已明確說明貧血模型是一種反模式,它根本就不是物件導向的產物,違反物件導向的核心思想,而充血模型則契合物件導向的思想。換句話說,貧血模型中只有屬性,不存在領域操作,僅僅是被當做一種資料結構來使用。而充血模型有血有肉,既有屬性,又有領域操作,貫徹物件導向的思想。囉嗦了......
又想起來一點,在閱讀池建強老師在infoq上的領域驅動設計和實踐與《領域驅動設計-軟體核心複雜性應對之道》一書過程中才恍然大悟到:運用面嚮物件語言不一定編寫出來的就是物件導向的程式。然覺悟到,原來從開始到現在所編寫的程式統統與物件導向有出入,乃純粹的面向事物指令碼或者說程式導向化程式設計。
言歸正傳,首先我們從使用貧血模型與使用充血模型程式設計時,各自分包的特點。
貧血模型的包結構,如下圖:
這個結構是我在實際開發中使用的,大家看著是否有熟悉的感覺!解釋起來也很輕鬆,entity自然就是放實體(也就是貧血模型聚居地),dao自然是與資料庫或其他持久策略互動的地方,service作為整個應用的核心,負責處理所有的邏輯,處理完成後交給dao做持久,controller起到排程的作用,其實就是一個個servlet。嘿嘿,熟悉吧,MVC模式。
《領域驅動設計》中建議的,使用充血模型時的包結構(更準確的說是DDD的包結構),如下圖:
這個解釋起來就比上一個要費勁很多,用兩幅借來的圖解釋。 圖中就是DDD中的四層結構,互動關係如下圖:
四層的職責分配:
其中值得注意的地方是,應用的核心是領域層。
呀,說了好多,也不知道大家是否能理解,估計看到這裡都厭煩了吧。
稍微總結一下:我的體會是,貧血模型與充血模型之間的差別一定程度上造成了程式導向化程式設計和麵向物件程式設計的兩個分支。那麼差別到底是什麼呢?說起來很簡單,就是業務邏輯由誰去處理。貧血模型僅僅被當做資料結構來使用,而充血模型會持有業務邏輯方法。
其次我們通過一個例子,展現一個貧血模型與充血模型中顯而易見的差別。 場景:一個網上銀行的簡單示例,要求可以存錢、取錢、轉賬、操作成功後需傳送郵件給持卡人。要求提供貧血模型和充血模型兩種實現方式。(例子不是那麼恰當,網銀怎麼取錢、存錢啊,只為說明問題。)
首先我們看下貧血模型是如何實現的。
類圖如下:
轉賬序列圖如下:
在互動的過程中,你會發現,核心確實是service層。
其他部分都省略,在圖中也能看個究竟,只貼出Account.java的程式碼:
public class Account implements Serializable {
private static final long serialVersionUID = 2248636870918341727L;
private String accountNum;
private int totalAcount;
private String name;
private String cardNum;
//此處省略get、set方法
}
充血模型的實現
類圖如下:
轉賬序列圖:
同樣貼出Account.java:
public class Account implements Serializable {
private static final long serialVersionUID = -4767597926507768285L;
private AccountRepository accountRepository;
private String accountNum;
private int totalAcount;
private String name;
private String cardNum;
public void deposit(int mony){
if(mony <= 0)
return;
this.totalAcount = this.totalAcount + mony;
accountRepository.updateAccount(this);
}
public void withdrawal(int mony){
if(mony <= 0)
return;
if(mony > this.totalAcount)
return;
this.totalAcount = this.totalAcount - mony;
accountRepository.updateAccount(this);
}
}
不知道大家看出來區別了沒有,充血模型是有血有肉的,核心領域方法都放到模型去去,而不是把領域方法放到模型之上的service層中去。
引用別人對貧血模型和充血模型的總結(沒記錯的話應該是javaeye的robbin總結的):
對於Java來說,更加適合採用貧血的模型,Java比較適合於把一個複雜的業務邏輯分離到n個小物件中去,每個小物件描述單一的職責,n個物件 互相協作來表達一個複雜的業務邏輯,這n個物件之間的依賴和協作需要通過外部的容器例如IoC來顯式的管理。但對於每個具體的物件來說,他們毫無疑問是貧 血的。
這種貧血的模型好處是:
1、每個貧血物件職責單一,所以模組解藕程度很高,有利於錯誤的隔離。
2、非常重要的是,這種模型非常適合於軟體外包和大規模軟體團隊的協作。每個程式設計個體只需要負責單一職責的小物件模組編寫,不會互相影響。
貧血模型的壞處是:
1、由於物件狀態和行為分離,所以一個完整的業務邏輯的描述不能夠在一個類當中完成,而是一組互相協作的類共同完成的。因此可複用的顆粒度比較 小,程式碼量膨脹的很厲害,最重要的是業務邏輯的描述能力比較差,一個稍微複雜的業務邏輯,就需要太多類和太多程式碼去表達(針對我們假定的這個簡單的工時管 理系統的業務邏輯實現,ruby使用了50行程式碼,但Java至少要上千行程式碼)。
2、物件協作依賴於外部容器的組裝,因此裸寫程式碼是不可能的了,必須藉助於外部的IoC容器。
對於Ruby來說,更加適合充血模型。因為ruby語言的表達能力非常強大,現在用ruby做企業應用的DSL是一個很熱門的領域,DSL說白了就是用來描述某個行業業務邏輯的專用語言。
充血模型的好處是:
1、物件自洽程度很高,表達能力很強,因此非常適合於複雜的企業業務邏輯的實現,以及可複用程度比較高。
2、不必依賴外部容器的組裝,所以RoR沒有IoC的概念。
充血模型的壞處是:
1、物件高度自洽的結果是不利於大規模團隊分工協作。一個程式設計個體至少要完成一個完整業務邏輯的功能。對於單個完整業務邏輯,無法再細分下去了。
2、隨著業務邏輯的變動,領域模型可能會處於比較頻繁的變動狀態中,領域模型不夠穩定也會帶來web層程式碼頻繁變動。
好像在裡面摻雜了好多東西,如果大家覺得羅嗦,可以看看這篇講領域模型的文章,比我寫的好,哈哈。連結在此:http://www.blogjava.net/GandofYan/archive/2006/05/30/48954.html
其實僅僅就這個例子的兩種實現,還有太多太多的東西要去講,但不是本篇的重點。學海無涯啊,領域驅動設計這方面的文章,在看書的過程中有任何感悟時我會陸續發到社群中。
參考資料:http://www.infoq.com/cn/articles/cjq-ddd
《領域驅動設計-軟體核心複雜性應對之道》
相關文章
- 貧血模型與充血模型比較 - DDD - The Domain Driven Design模型AI
- 從貧血模型到充血模型模型
- 貧血模型 - DDD - The Domain Driven Design模型AI
- 聊一聊領域驅動與貧血模型模型
- 談DDD與貧血領域模型:再次為失血模型辯護 -Codecentric AG部落格模型
- OSI模型 與 DOD模型的比較模型
- AI模型對比AI模型
- Martin Fowler大神 - 微服務、貧血模型、重構、敏捷開發方法論微服務模型敏捷
- 開源OCR模型對比模型
- 全面對比:天工大模型 vs 紫東太初大模型大模型
- 訊飛星火大模型 與New Bing實測對比大模型
- 英文DDD培訓線上課程推薦: 從失血模型重構到充血模型模型
- 如何使用充血模型實現防彈程式碼 - DZone Java模型Java
- 在表格中基於樹的模型與深度學習優劣對比模型深度學習
- 五種IO模型介紹和對比模型
- 規則引擎與ML模型的比較 - xLaszlo模型
- MNN模型輸出與ONNX模型輸出對不上模型
- Adjacent List Model 與 Nested Set Model 兩種無限分類模型的對比模型
- 如何管理資料模型與業務模型之間對映?模型
- 可用於資料庫對比評估的FURPS+模型資料庫模型
- 視覺化經典模型的對比實驗總結視覺化模型
- espnet中的transformer和LSTM語言模型對比實驗ORM模型
- 重要 | Spark和MapReduce的對比,不僅僅是計算模型?Spark模型
- 併發模型比較模型
- 機器學習引數模型與非引數模型/生成模型與判別模型機器學習模型
- css盒子模型與盒模型的浮動CSS模型
- 業務流程模型與資料流程圖的比較 - brcommunity模型流程圖Unity
- 元學習:人類與大模型比較建模大模型
- SemanticKernel/C#:使用Ollama中的對話模型與嵌入模型用於本地離線場景C#模型
- DDD中簡單模型比複雜模型更危險模型
- 第03講:Flink 的程式設計模型與其他框架比較程式設計模型框架
- 對抗深度學習: 魚 (模型準確性) 與熊掌 (模型魯棒性) 能否兼得?深度學習模型
- 盒模型與BFC模型
- Netty與Reactor模型NettyReact模型
- Laravel 之多對多的關係模型Laravel模型
- dotnet 將本地的 Phi-3 模型與 SemanticKernel 進行對接模型
- OSI 七層模型與 TCP IP 五層模型模型TCP
- OSI七層模型與TCP/IP五層模型模型TCP
- C#開發BIMFACE系列41 服務端API之模型對比C#服務端API模型