貧血模型與充血模型的對比

劉曉日發表於2011-08-31

上週翻譯了MartinFowler的貧血模型,當即就答應溫老師了溫謙老師提出能不能寫個示例來解釋一下貧血模型的要求。在網上也查了一些資料,本想轉載一篇,轉念一想,算了吧,按自己的理解去寫吧。不論對與錯,為大家提供一個靶子,同意也好,反對也罷,希望大家也把自己的見解記錄在這裡。

在這裡再次宣告下,本人功力尚淺,難免有理解不到位的地方,所以還請大家在閱讀的時候發現並指正。

打算藉助事物指令碼和領域模型兩種方式來揭開貧血與充血之間的異同。

我們先了解一下事物指令碼和領域模型的概念。

事物指令碼: 事務指令碼的核心是過程,通過過程的呼叫來組織業務邏輯,每個過程處理來自表現層的單個請求。大部分業務應用都可以被看成一系列事務,從某種程度上來說,通過事務指令碼處理業務,就像執行一條條Sql語句來實現資料庫資訊的處理。事務指令碼把業務邏輯組織成單個過程,在過程中直接呼叫資料庫,業務邏輯在服務(Service)層處理。

領域模型: 領域模型的特點也比較明顯, 屬於物件導向設計,領域模型具備自己的屬性行為狀態,並與現實世界的業務物件相對映。各類具備明確的職責劃分,領域物件元素之間通過聚合和引用等關係配合解決實際業務應用和規則。可複用,可維護,易擴充套件,可以採用合適的設計模型進行詳細設計。缺點是相對複雜,要求設計人員有良好的抽象能力。

大家很奇怪吧,標題不是“貧血模型與充血模型的對比”嗎,怎麼扯到事物指令碼和領域模型上了呢。我是這樣理解的:在面向事物指令碼程式設計中所使用的物件就是我們今天的主角之一“貧血模型”,當然領域模型自然也就對應著“充血模型”。那麼他們最大的區別在哪裡呢?其實Marth Folwer的貧血模型一文中已明確說明貧血模型是一種反模式,它根本就不是物件導向的產物,違反物件導向的核心思想,而充血模型則契合物件導向的思想。換句話說,貧血模型中只有屬性,不存在領域操作,僅僅是被當做一種資料結構來使用。而充血模型有血有肉,既有屬性,又有領域操作,貫徹物件導向的思想。囉嗦了......

又想起來一點,在閱讀池建強老師在infoq上的領域驅動設計和實踐與《領域驅動設計-軟體核心複雜性應對之道》一書過程中才恍然大悟到:運用面嚮物件語言不一定編寫出來的就是物件導向的程式。然覺悟到,原來從開始到現在所編寫的程式統統與物件導向有出入,乃純粹的面向事物指令碼或者說程式導向化程式設計。

言歸正傳,首先我們從使用貧血模型與使用充血模型程式設計時,各自分包的特點。

貧血模型的包結構,如下圖:

enter image description here

這個結構是我在實際開發中使用的,大家看著是否有熟悉的感覺!解釋起來也很輕鬆,entity自然就是放實體(也就是貧血模型聚居地),dao自然是與資料庫或其他持久策略互動的地方,service作為整個應用的核心,負責處理所有的邏輯,處理完成後交給dao做持久,controller起到排程的作用,其實就是一個個servlet。嘿嘿,熟悉吧,MVC模式。

《領域驅動設計》中建議的,使用充血模型時的包結構(更準確的說是DDD的包結構),如下圖:

enter image description here

這個解釋起來就比上一個要費勁很多,用兩幅借來的圖解釋。 圖中就是DDD中的四層結構,互動關係如下圖:

貧血模型與充血模型的對比

四層的職責分配:

enter image description here

其中值得注意的地方是,應用的核心是領域層。

呀,說了好多,也不知道大家是否能理解,估計看到這裡都厭煩了吧。

稍微總結一下:我的體會是,貧血模型與充血模型之間的差別一定程度上造成了程式導向化程式設計和麵向物件程式設計的兩個分支。那麼差別到底是什麼呢?說起來很簡單,就是業務邏輯由誰去處理。貧血模型僅僅被當做資料結構來使用,而充血模型會持有業務邏輯方法。

其次我們通過一個例子,展現一個貧血模型與充血模型中顯而易見的差別。 場景:一個網上銀行的簡單示例,要求可以存錢、取錢、轉賬、操作成功後需傳送郵件給持卡人。要求提供貧血模型和充血模型兩種實現方式。(例子不是那麼恰當,網銀怎麼取錢、存錢啊,只為說明問題。)

首先我們看下貧血模型是如何實現的。

類圖如下:

貧血模型與充血模型的對比

轉賬序列圖如下:

貧血模型與充血模型的對比

在互動的過程中,你會發現,核心確實是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

  《領域驅動設計-軟體核心複雜性應對之道》

相關文章