31 May 2006

Domain Driven Design: SPECIFICATION (規格)

SPECIFICATION: 一物件陳述另一個物件在狀態上的限制。SPECIFICATION 最主要的用途是檢驗另一個物件是否滿足指定的條件。

企業邏輯通常不會剛好符合任何顯而易見的 ENTITIES 或是 VALUE OBJECTS,而且它們的多樣性與組合會掩蓋掉原本 domain object 的基本涵意。然而將邏輯移出 domain 層只會更糟... 因為 domain 的程式碼將不再能表達 model 的意圖了。

Logic programming 提供分解,可組合的邏輯物件,稱為 predicates。但是如果將這種觀念用 OOP 來實做相當的麻煩。況且 predicate 太過於一般化了,它無法比專屬的設計傳達出更清楚的目的。

我們可以借用 predicate 的觀念,做一些簡化並以 VALUE OBJECTS 的方式實作 -- 這就是 SPECIFICATION。

如果建立 SPECIFICATION 需要太多外部的物件,可以用 FACTORY 隱藏建立的邏輯,以避免不必要的關聯。

SPECIFICATION 可應用在三個領域,並且 "一併解決":

  • validation: 確認物件的狀態是否合乎某些條件。
  • selection: 從一群物件中篩選出符合某些條件的物件。
  • build to order: 創建符合某些條件的物件 (或是重整物件)。

SPECIFICATION 實作上,希望能將複雜的邏輯判斷集中在一個 class 裡,而這個 class 能明確地表達出 domain 上的某個意圖或是需求。下面為一可能的實作:
//判斷發票是否拖欠的規格。 (這是個 VALUE OBJECTS,用完就丟)
public class DelinquentInvoiceSpecification {

   //檢查發票是否拖欠
   public boolean isSatisfiedBy(Invoice candidate) {
       if( candidate.getDueDate() < currentDate) 
          ....

       if(customer.... ) ...
   }
   
   //如果這個規格要用在查詢上的話,可在此將規格轉成 SQL/HQL
   public String asHQL() {
      return " select * from Invoice where .... " ;
   } 

   //上面的方法會跟 db 產生關聯,如果覺得不妥可換成下面的寫法:
   //將拖欠的邏輯 delegate 到 invoiceDAO
   public List<Invoice> satisfyingElementsFrom(InvoiceDAO dao) {

      //利用 dao 查出超過寬限期限的發票;dao 只知道依這個條件查,但
      //它不懂這個在 domain 上有何意義。是這個 SPECIFICATION 付與它意義的。
      return dao.findInvoiceWhereGracePeriodPast(currentDate);
   }

}

上面的例子原本在程式中,可能僅是一連串的 if 來判斷 "發票是不是已經過期卻還未結清"。現在我們替這段邏輯 "升級",讓它變成一個獨立 class,明確地反應 domain 中 "發票拖欠" 這件事。並且將原本散佈在資料庫的查詢也集中在這裡。如此一來,不僅程式碼簡單明瞭,未來要修改或維護拖欠的邏輯時,只需專注在這個 class 即可。

第三個 SPECIFICATION 的應用: "build to order"... 我看了老半天還是不懂書上在寫啥.... orz

ps. 上述 memo 許多部份是直接翻譯自 DDD 裡面的文句,這本書真的很難... 硬把他翻譯成中文後,還要反覆唸個幾次才能稍微看懂它的涵意....


回響

可以用 Tag <I>、<B>,程式碼請用 <PRE>