貧血領域模型

貧血領域模型（anemic domain model）是一種軟體開發的模型，是指其領域物件只有部份业务逻辑（例如資料驗證、計算、規則等），甚至沒有任何业务逻辑。像以下的模型就是貧血領域模型，有資料讀取和寫入的介面函式，但沒有其他函式。

class Rectangle
{
    public int Height { get; set; }
    public int Width { get; set; }
}

貧血領域模型的业务逻辑會放在程式的架構或是其他組件中，會讓重構和維護變得困難費時。在面向对象程序设计中將此視為反面模式。

簡介
此模式最早是由马丁·福勒提出，他當時就認為這是反面模式：

在貧血領域設計中，業務邏輯是另外用類別來實現，以此轉換領域物件的狀態。福勒將這些外部類別稱為事務腳本（transaction scripts），此模式常見於Java應用程式裡，像的早期版本也鼓勵此一作法，在.NET應用程式中也很盛行，這些應用程式是依循三層服務應用架構，將這些物件放在「業務實體」的分類裡（不過業務實體也可以包括行為）。

福勒認為事務腳本模式是：

福勒在《Patterns of Enterprise Application Architecture》中提到事務腳本模式可能適合小的業務應用，省去了複雜了物件導向資料庫映射層。

貧血領域模型可能出現在受服務導向架構影響的系統中，其行為不會（或不傾向）跟著資料流動，像Messaging架構，pipeline架構，或SOAP/REST API，都屬於其行為不跟著資料流動的例子。像COM+和Remoting的架構允許有行為，但越來越多的網站傾向不連線，無狀態（stateless）的架構。

上述論點的批評
有關將貧血領域模型視為反面模式的看法，也有些人有不同的意見，有些人也看到其中的優點，例如：

 清楚的區分邏輯和資料
 在簡單的應用程式中運作良好
 應用程式是無狀態的邏輯，有利於橫向擴展
 不需要複雜的物件導向資料庫映射層
 和映射和注入框架的相容性更高，這些框架期望使用靜態屬性，而不是特定的建構函數或屬性填充順序

常見的論點是貧血領域模型比較容易依照SOLID原則中的S："S表示单一功能原则，認為類別應該做一件事，而且將這件事做好(...)" 不過，依照Robert C. Martin的觀點，這是對此原則的誤解：在所有SOLID的原則中，单一功能原则是最少人真正理解的。這可能是因為它有一個特別不適合的名稱，太容易讓程式設計者看到名稱，就假設每一個模組應該做一件事，沒錯，的確有這樣的原則，一個函式應該做一件事，而且只做一件事。我們在將大的函式重構成小函式時就是使用此原則：甚至用到最底層，但這不是SOLID原則中的一項，這不是单一功能原则(...)。单一功能原则的最終版本是：每一個模組應該對一個提出需求的角色（actor），提供服務，也只對這一個角色提供服務"
風險
若使用貧血領域模型，程式設計者需考慮以下的風險：

 無法以真正物件導向的作法實現邏輯
 違反物件導向的封裝和資訊隱藏的原則。
 需要獨立的業務層來包括不在领域模型裡的邏輯，這也代表领域模型物件任何時候都無法確保其正確性，因為其驗證和變異邏輯都在其他模組裡（可能還分散在多處）
 若要在物件模型的不同消費者中共享資料，需要服務層。
 模型的表現力較低，無法清晰、直觀地傳達所代表的業務概念或邏輯。
 範例 
貧血領域模型可能會像以下的方式撰寫程式，程式本身沒有包括任何業務的考量（以此例中，包括高度和寬度不能是負數或是零，或是有其他有關正方形面積的需求。這表示這些機能是在其他模組中實現的，不在此程式的業務層，而是藏在架構其他的部份。

class Rectangle
{
    public int Height { get; set; }
    public int Width { get; set; }
}

非貧血領域模型的寫法如下。會將業務考量加到領域物件上，此時的架構可以更不受領域影響。這讓程式可以假設物件的一些屬性成立，不用在架構中的某處來實現此屬性的檢查程式。

class Rectangle
{
    public Rectangle(int height, int width)
    {
        SetHeight(height);
        SetWidth(width);
    }

    public int Height { get; private set; }
    public int Width { get; private set; }

    public void SetHeight(int height)
    {
        if (height <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(height));
        }

        Height = height;
    }

    public void SetWidth(int width)
    {
        if (width <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(width));
        }

        Width = width;
    }

    public int CalculateArea()
    {
        return Height * Width;
    }
}

相關條目
 （POJO）
 領域驅動設計
 GRASP裡的資料專家

參考資料

外部連結
Anemic Domain Model by Martin Fowler
Three-Layered Services Application
Application Architecture for .NET: Designing Applications and Services
Article on why anemic model may be considered good design
Writing Clean Code in ASP.NET
How to Avoid Anemic Domain Model
Category:软件架构
Category:反模式