天天看點

貧血模型和充血模型貧血模型(Anemic Domain Model)充血模型(Rich Domain Model)失血模型漲血模型為什麼貧血模型受歡迎什麼場景下使用充血模型

貧血模型和充血模型

  • 貧血模型(Anemic Domain Model)
    • 舉例(MVC架構)
    • 貧血模型的優點
    • 貧血模型的缺點
    • 說明
  • 充血模型(Rich Domain Model)
    • 優點
    • 缺點
  • 失血模型
  • 漲血模型
  • 為什麼貧血模型受歡迎
  • 什麼場景下使用充血模型

貧血模型(Anemic Domain Model)

  • 貧血模型是一種領域模型,其中領域對象包含很少或沒有業務邏輯。是一種面向過程的程式設計模式,它與面向對象設計的基本思想相悖,将資料和過程結合在一起。
  • 因為貧血模型沒有邏輯實作,是以邏輯基本上會放到調用貧血模型的service中,這些service類會轉換領域對象的狀态。
  • 貧血模型中,domain包含了不依賴于持久化的原子領域邏輯,而組合邏輯在Service層。service :組合服務,也叫事務服務。model:除包含get set方法,還包含原子服務(如獲得關聯model的id)。dao:資料持久化。

舉例(MVC架構)

MVC 三層架構中的 M 表示 Model,V 表示 View,C 表示 Controller。它将整個項目分為三層:資料層、展示層、邏輯層。

我們一般就将後端項目分為 Repository 層、Service 層、Controller 層。其中,Repository 層負責資料通路,Service 層負責業務邏輯,Controller 層負責暴露接口。

// Controller+VO(View Object) //
public class UserController {
  private UserService userService; //通過構造函數或者IOC架構注入
  
  public UserVo getUserById(Long userId) {
    UserBo userBo = userService.getUserById(userId);
    UserVo userVo = [...convert userBo to userVo...];
    return userVo;
  }
}

public class UserVo {//省略其他屬性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

// Service+BO(Business Object) //
public class UserService {
  private UserRepository userRepository; //通過構造函數或者IOC架構注入
  
  public UserBo getUserById(Long userId) {
    UserEntity userEntity = userRepository.getUserById(userId);
    UserBo userBo = [...convert userEntity to userBo...];
    return userBo;
  }
}

public class UserBo {//省略其他屬性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}

// Repository+Entity //
public class UserRepository {
  public UserEntity getUserById(Long userId) { //... }
}

public class UserEntity {//省略其他屬性、get/set/construct方法
  private Long id;
  private String name;
  private String cellphone;
}
           
  • 從代碼中我們可以看到UserVo,UserBo,UserEntity 隻包含資料,不包含業務邏輯的類,就叫作貧血模型。
  • 這種貧血模型将資料與操作分離,這種分離直覺上就是不在同一個類裡,就和一個車隻有車的特點,但是不能開一樣。破壞了面向對象的封裝特性,是一種典型的面向過程的程式設計風格。

貧血模型的優點

  • 貧血模型的系統層次結構清楚,各層之間單向依賴
  • 領域對象幾乎隻作傳輸媒體之用,不會影響到層次的劃分

貧血模型的缺點

  • 對象狀态和行為分離(貧血模型中,對象隻有屬性,get/set方法,業務邏輯在不在對象類内部),是以一個完整的業務邏輯描述不能在一個類中完成,而是一組互相協作的類共同完成的。
  • 不夠面向對象,領域對象隻是作為儲存狀态或者傳遞狀态使用,它是沒有生命的,隻有資料沒有行為的對象不是真正的對象。(在service裡面處理所有的業務邏輯,對于細粒度的邏輯處理,通過增加一層Facade達到門面包裝的效果)
  • 可複用的顆粒度比較小,代碼量膨脹很厲害,很重要的一點是業務邏輯的描述能力較差,一個稍微複雜的業務邏輯,就需要太多類和太多代碼去表達。

說明

在使用Spring的時候,通常暗示着你使用了貧血模型

充血模型(Rich Domain Model)

  • 資料和對應的業務邏輯被封裝到同一個類中。是以,這種充血模型滿足面向對象的封裝特性,是典型的面向對象程式設計風格。
  • 業務邏輯集中在 Service 類中。基于充血模型,Service 層包含 Service 類和 Domain 類兩部分。Domain 是基于充血模型開發的,既包含資料,也包含業務邏輯。而 Service 類變得非常單薄。
  • 充血模型中,絕大多業務邏輯都應該被放在domain裡面,包括持久化邏輯,而Service層是很薄的一層,僅僅封裝事務和少量邏輯,不和DAO層打交道。service :組合服務也叫事務服務;model:除包含get set方法,還包含原子服務和資料持久化的邏輯

優點

  • 對象自治度很高,表達能力強,适合于複雜的企業業務邏輯實作,可複用程度高。

缺點

  • 如何劃分業務邏輯,什麼樣的邏輯應該放在Domain中,什麼樣的業務邏輯應該放在Service 中,這是很含糊的。
  • 對象自治度高的結果就是不利于大規模團隊分工協作。

失血模型

  • 失血模型中,domain隻有屬性的get set方法的純資料類,所有的業務邏輯完全由Service來完成的,沒有dao,Service直接操作資料庫,進行資料持久化。失血模型service層負擔太重,一般不會有這種設計。

漲血模型

  • 脹血模型取消了Service層,隻剩下domain object和DAO兩層,在domain的domain logic上面封裝事務。

為什麼貧血模型受歡迎

  • 開發的系統業務可能都比較簡單。貧血模型就可以應付,不需要充血模型。大部分是基于 SQL 的 CRUD 操作,我們不需要動腦子精心設計充血模型,貧血模型足以應付這種簡單業務的開發工作。除此之外,因為業務比較簡單,即便我們使用充血模型,那模型本身包含的業務邏輯也并不會很多,設計出來的領域模型也會比較單薄,跟貧血模型差不多,意義不大。
  • 充血模型的設計要比貧血模型更加有難度。因為充血模型是一種面向對象的程式設計風格。我們從一開始就要設計好針對資料要暴露哪些操作,定義哪些業務邏輯。而不是像貧血模型那樣,我們隻需要定義資料,之後有什麼功能開發需求,我們就在Service 層定義什麼操作,不需要事先做太多設計。
  • 思維固化,轉型有成本。基于貧血模型的傳統開發模式經曆了這麼多年,已經深得人心、習以為常。如果轉向用充血模型、領域驅動設計,有一定的學習成本、轉型成本。

什麼場景下使用充血模型

  • 我們平時的開發,大部分都是 SQL 驅動(SQL-Driven)的開發模式。接到一個後端接口的開發需求的時候,就去看接口需要的資料對應到資料庫中,需要哪張表或者哪幾張表,然後思考如何編寫 SQL 語句來擷取資料。之後就是定義 Entity、BO、VO,然後模闆式地往對應的Repository、Service、Controller 類中添加代碼。
  • 業務邏輯包裹在一個大的 SQL 語句中,而 Service 層可以做的事情很少。SQL 都是針對特定的業務功能編寫的,複用性差。當我要開發另一個業務功能的時候,隻能重新寫個滿足新需求的 SQL 語句。
  • 對于簡單業務系統來說,這種開發方式問題不大。但對于複雜業務系統的開發來說,這樣的開發方式會讓代碼越來越混亂,最終導緻無法維護。
  • 在這種開發模式下,我們需要事先理清楚所有的業務,定義領域模型所包含的屬性和方法。領域模型相當于可複用的業務中間層。新功能需求的開發,都基于之前定義好的這些領域模型來完成。

繼續閱讀