# 隔絕相依性的方式

參考資料:([https://dotblogs.com.tw/hatelove/archive/2012/11/27/learning-tdd-in-30-days-day6-several-ways-to-isolate-object-dependency-and-easy-for-testing.aspx](https://dotblogs.com.tw/hatelove/archive/2012/11/27/learning-tdd-in-30-days-day6-several-ways-to-isolate-object-dependency-and-easy-for-testing.aspx))

## 建構式（constructor）

將物件相依的介面，拉到建構式供外部物件使用，可自行組合目標物件的相依實體。屬性為private。

### 好處:

#### 有許多現成套件、框架可以用。

有許多DI framework 支援 Autowiring.

Autowiring is an automatic detection of dependency injection points.

這邊的 dependency injection points 在這例子，指的就是建構式。

由建構式傳入相依介面的實體物件，是一個很通用的方式。因此在結合許多常見的 DI framework，不需要再額外處理。

(以 Unity 為例，在 UnityContainer 取得目標物件時，會自動尋找目標物件參數最多的建構式。並針對每一個參數的型別，繼續在 UnityContainer 中尋找對應的實體物件，直到目標物件組合完畢，回傳一個完整的目標物件。)

### 顧慮點:

#### 物件太多、參數不同 容易搞混，若方法中沒有防呆，容易出現錯誤。

當物件越來越複雜時，建構式也會趨於複雜。倘若沒有 DI framework 的輔助，則使用物件上，面對許多 overload 的建構式，或是一個建構式參數有好幾個，會造成使用目標物件上的困難與疑惑。若沒有好好進行 refactoring，也可能因此而埋藏許多 bad smell。

另外，倘若是許多建構式，也可能造成要呼叫 A 方法時，應選用 A 對應的建構式，但在使用物件上，可能會用錯建構式而不自知，若方法中沒有正確的防呆，則可能出現錯誤。（請搭配單元測試的測試案例來輔助）

最後，與原本直接相依的程式碼相比較，目標物件的相依物件因此暴露出來，交由外部決定，而喪失了一點封裝的意味。而使用端也不一定知道，要取用此物件時，應該要注入哪些相依物件。（請使用 Repository Pattern 或 DI framework 來輔助）

## 公開屬性（public setter property）

#### 一樣用 interface當型別，setter 設 public，getter 設 private。

其實公開屬性與公開建構式非常類似，透過 public 的 property（property 型別仍為 interface），讓外部在使用目標物件時，可先 setting 目標物件的相依物件，接著才呼叫其方法。

而公開屬性通常只會將 setter 公開給外部設定，getter 則設定為 private。原因很簡單，外部只需設定，而不需取用。就像公開建構式，在使用物件之前先傳入初始化物件必備的資訊，但目標物件可能將這些資訊，存放在 private 的 filed 或 property 中，而不需再提供給外部使用。

### 好處:

#### 也有許多DI framework，不需要改建構式。

同樣的，public property 也是常見的 dependency injection points，所以也有許多 DI framework 支援。另外則是不需要對建構式進行改變，或增加新的建構式。對過去已經存在的 legacy code 的影響，會比建構式的方式小一點點（但幾乎沒有太大差異）。

### 顧慮點:

#### 使用前應檢查該屬性是否為null，並進行 error handing，避免發生錯誤仍執行不應執行的程式碼。

最常見的情況，就是使用目標物件時，相依介面應有其對應執行個體，但卻因為使用端沒有設定 public property，導致使用方法時出現 NullReferenceException，這種情況也怪不了使用端，因為使用端極有可能本就不瞭解這個方法中，有哪些相依物件。

解決方式與建構式的建議雷同，首先當然要有測試程式來說明（測試程式就是物件使用說明書），另外取得目標物件，仍可透過 Repository Pattern，讓使用端無須瞭解目標物件的相依關係。

並且在方法中使用相依介面前，應檢查其是否為 null，若為 null，則代表參數設定錯誤，進行 error handling，避免已經發生錯誤仍執行許多不應執行的程式碼。或是在 property 的 getter 時，檢查是否為 null 或當為 null 時，給予一預設值，以避免方法無法正常執行。（視實際需求而定）

另外，公開屬性的方式，也如同公開建構式一般，破壞了一點點物件封裝的用意。但這兩者，都是 IoC 設計會帶來的影響。

## 呼叫方法時傳入參數（function parameter）

#### 將方法相依的物件丟到參數，相對其他方法 不容易出錯。

既然前面兩種方式，都可能造成使用方法時，可能沒有設定好相依介面的執行個體，導致發生錯誤。或是使用目標物件時，不知道該呼叫哪一個建構式或初始化哪些屬性。那很簡單的方式，就是把方法相依介面的部分，拉到方法的參數上。方法中，需要使用到哪些介面，強迫由呼叫端必須給定參數。目標物件的方法內容則僅相依於參數上的介面。

### 好處:

不必再擔心要先初始化哪些 property，或呼叫哪一個建構式。當要呼叫某一個方法，其相依的物件，就是得透過參數來給定。基本上也不太需要擔心使用上造成困擾或迷惑。

### 顧慮點:

#### 方法簽章不穩定，當需求異動時，影響範圍較大，DI framework支援較低。適合用在非常明確、穩固的方法，避免前面提到的問題。但這個方式可以與其他方式，可以衡量狀況適度使用。

最大的問題，在於方法簽章上的不穩定性。當需求異動，該方法需要額外相依於其他物件時，方法簽章可能會被迫改變。而方法簽章是物件導向設計上，最需要穩定的條件之一。以物件導向、介面導向設計來說，當多型物件方法簽章不一致時，向來是個大問題。

另外，方法的參數過多，在使用上也會造成困擾。而且會影響到 legacy code 的呼叫端，需要全面跟著異動，才能編譯成功。

而且透過參數的方式，DI framework 支援度較低。

但這不代表，就不能在方法參數中，傳入相依物件。在 .net framework 還是有許多這樣的設計，例如：List.Sort 方法 (IComparer)。這樣的設計方式，通常要確保該方法相依相當明確、穩固，避免上述問題。

by the way, 這個方式是可以與其他方式共存的，所以在設計物件時，可衡量搭配使用。

## 可覆寫的保護方法（protected virtual function）

利用 protected virtual 去繼承欲測試的類別

### 好處:

這個方式最大的好處，是完全不影響外部使用物件的方式。僅透過 protected 與 virtual 來對繼承鏈開放擴充的功能，並且透過這樣的方式，就使得原本直接相依而導致無法測試的問題，獲得解套。

### 顧慮點:

這是為了測試，且面對 legacy code 所使用的方式，而不是良好的物件導向設計的方式。IoC 的用意在於介面導向與擴充點的彈性，所以當可測試之後，倘若重構影響範圍不大，建議讀者朋友還是要將物件改相依於介面，透過 IoC 的方式來設計物件。

by the way, 同樣為了解決直接相依物件，甚至相依於 static 方法、.net framework 本身的物件（如 DateTime.Now）而導致無法測試的問題，還有另外一個方式，稱為 fake object。這在後面的文章，會再進行較為詳盡的介紹。
