13 July 2005

續:

o 保持 domain layer 乾淨

  • 注意物件的封裝、小心違反 Law of Demeter:讓我們看一下一個 Service Facade 的 method :
    public class OrderServiceImpl implements OrderService { //Service facade
    
       //facade method,我們在 Spring 裡會設定這個 method 需要開啟 Transaction
       //故此處不需要寫有關 Transaction 的程式碼 
       public Order saveOrder(ShoppingCart cart, User buyer) {
    
           List boughtItemIds = cart.getItemIds() ;
    
           //進入 Domain layer 前,轉換成 Domain 物件
           List products = productDAO.findProducts(boughtItemIds) ;
           
           //========== domain layer start
           Order newOrder = new Order() ; 
           newOrder.setBuyer(buyer);
           newOrder.setProducts(products); 
    
           //違反 Law of Demeter
           int cost = buyer.getRole().getDiscountRate() * 250 * products.size() ;
    
           newOrder.setCost(cost) ;
           //========== domain layer end
    
           orderDAO.save(newOrder) ;         
           return newOrder ;
       }
    }
    

    上面的程式中,method 一開始便取得 shoppingCart 以及購買人,再藉由 productDAO 將購買人選擇的產品 id 轉換為 Domain 的 Entity -- Product,然後再進入 Domain Layer,開始進行 Order 的企業邏輯。最後直到 Order 物件完成,再由 orderDAO 負責儲存。

    整個架構看起來沒啥問題,有善用 DAO 讓 Domain Layer 不涉及 Hibernate 相關的操作。但 Domain Layer 裡到是犯了不少錯... 首先是 Order 物件完全沒有封裝,都是直接用 setter 操作。再來是 buyer 連續執行 getter,取得藏在 User 深兩層的資料,違反了 Law of Demeter。Service Facade 無原無故就 depend "Role" 這個物件 (buyer.getRole()),白白地增加 coupling....比較好的寫法應該將 Domain Layer 封裝:


       public Order saveOrder(ShoppingCart cart, User buyer) {
           List boughtItemIds = cart.getItemIds() ;
           List products = productDAO.findProducts(boughtItemIds) ;
           
           //========== domain layer start
           Order newOrder = new Order(buyer, products) ; 
           //========== domain layer end
    
           orderDAO.save(newOrder) ;         
           return newOrder ;
       }
    

    它的 setter 全都要拿掉 (即scope設成 private or package private),所有的邏輯都應該封裝在 Order 物件裡,在裡面處理各個 Domain 物件間的關聯。在這裡,我們視 "Order" 為 Root of Entity,他位於整個邏輯的最上層。在最佳的情況下,facade 裡應該只會與一個或是數個 Root 物件溝通,這樣才能夠讓 facade 這一層穩定,而不會因為,例如,錢計算的方式改了,就要動到 facade。

    從另一個角度來看:在Struts+Spring的架構下,我們會讓 Action 去呼叫 Service Facade 的 method。由於 Action 在 container 內只維持單一個 instance,為了讓 Action 及 Service Facade 達到 Thread-safe,Facade 通常得設計成 stateless 並小心維護其 thread-safty。在這雙重限制下,很多 OOP 的技巧、pattern 都無法在 facade 裡發揮,而只能寫像是 procedure 般的程式... 因此 facade 基本上不該太多的 domain logic,而應該讓 Root of Entity 去處理。

    第三個原因就很明顯了 -- 請問哪一種的 Unit Test 好寫?答案顯而易見。如果採用的是 TDD 的方式開發,要出現第一種的寫法還不大容易咧!

待續....


回響

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