.NET Core TDD 前傳: 編寫易於測試的程式碼 -- 依賴項

solenovex發表於2018-07-30

第1篇: 講述了如何創造"縫".  "縫"(seam)是需要知道的概念.

第2篇, 避免在構建物件時寫出不易測試的程式碼.

本文是第3篇, 講述依賴項和迪米特法則.

 

迪米特法則 (Law of Demeter)

還是使用建造汽車的例子. 生產汽車的時候需要輪胎, 組裝時需要什麼型號的輪胎, 就請求該型號的輪胎, 然後相關人員會從庫房把該型號的輪胎送到產線用於組裝. 

我相信很少有汽車廠會這樣做: 生產汽車時, 汽車組裝工拿著庫房的鑰匙, 自己去庫房從各種各樣的輪胎中找所需要的型號..

這就是違反迪米特法則的一個例子.

 

迪米特法則大概的意思是: "只訪問你自己建立的物件, 或者作為引數傳給你的物件. 不要通過其它物件間接的訪問物件"

用一句話歸納迪米特法則就是: "只與直系朋友交談, 不要和陌生人交談".

 

注意: 迪米特法則其實並不算嚴格的法則, 它只是一個非常有益的指導性原則. 

 

存在的問題

用程式碼形容上面的例子就是: 

這違反了迪米特法則, 導致了以下問題:

  • 造成了BenzCar和Warehouse以及MichelinTire之間的緊耦合, 而實際上BenzCar只需要MichelinTire.
  • 測試時, 設定會很麻煩. 程式碼裡Warehouse是直系朋友, MichelinTire是陌生人. 我們需要為Warehouse和MichelinTire同時設定測試替身.
  • 真正需要的依賴項沒有明確在建構函式裡定義. 這裡Warehouse相當於是一個容器, 測試時, 我們可能會不知道要為Warehouse裡的哪個東西做測試替身.

 

危險訊號

下列寫法可能意味著您的程式碼違反了迪米特法則:

  • 程式碼裡有這樣的呼叫: "warehouse.getTire.getMichelinTire", 有一連串的點".". 但是有時候這樣做是可以的, 例如流暢(fluent)形式的建造者模式就可以, 因為fluent介面通常會返回物件本身, 然後再去使用該物件.
  • 依賴於容器. 例如把 IocContainer作為依賴注入使用. 
  • 依賴項的名稱為XxxContext, XxxContainer, XxxEnvironment, XxxManager, XxxServiceLocator.
  • 測試時需要建立返回mocks的mock物件.
  • 測試時的設定非常麻煩.

 

解決辦法

解決辦法就是遵從迪米特法則.

只注入我們直接需要的依賴項, 直接使用它們. 這樣就會保證依賴項很明確, 測試的時候一眼就能看出依賴於哪些物件.

程式碼示例

例子一

下面這個違反了迪米特法則, 直接注入的是Warehouse, 而實際用到的卻是MichelinTire:

 

正確的做法是, 注入直接使用的依賴項:

 

例子二

下面的程式碼也違反了迪米特法則, 它注入了一個容器類的物件:

這個ServiceLocator就相當於是一個容器. 這樣用的話, 寫測試的人可能根本無法知道需要使用容器裡面的哪個物件.

你也許會說這樣做靈活(我以前也經常這樣做), 但是重構的時候, 這裡很容易出錯, 因為根本看不出來真正依賴的是哪個物件.

 

正確的做法還是應該注入直接需要的依賴項:

 

Law of Demeter相關的內容就簡單介紹這些.

相關文章