我們在寫物件導向程式時,一定會常常呼叫到其他人寫的程式,也就是說你的程式裡一定會建立一個別人程式的 instance. 例如,你的程式裡有一個 class 叫 DataWriterHelper ,裡面有一個 WriteSecret(),這方法裡面的內容其實是透過其他人寫的程式來達成.假設其他人寫的 class 叫 SecretHelper,而裡面有一個 Write().所以你的程式一開始看起來如下:
class DataWriterHelper { public void WriteSecret(string data) { SecretHelper _helper = new SecretHelper(); _helper.Write(data); } }
這樣子就可以很清楚的看到你的程式 DataWriterHelper 和別人的程式 SecretHelper 有一個關係了,換句話說,你的 DataWriterHelper 依賴了 SecretHelper,因為若 SecretHelper 不存在的話,你的 WriteSecret() 便發揮不了功能.
但這樣寫,有什麼不對嗎 ? 好像沒什麼不對,只是失去了一些彈性.如果 SecretHelper 改了一個版本,把 Write() 改成 WriteData(),那麼你的程式也就必須跟著變動.另外,當你要測試 WriteSecret() 時,你會發現你非得把 SecretHelper 的元件也一起加入到測試專案才行,因為你的程式依賴著 SecretHelper.如果你將它 Mock,也是一種可行的方法,只怕真實情況不是一個 Mock 就能滿足你的需要.
接下來,說明什麼叫降低 dependency. 降低的方法可以用一個神奇的東西 - Interface. 首先,先定義好 Interface 的內容.
interface IWriteSecret { void WriteSecret(string data); }
這個 Interface 定義好之後,理論上就應該不會輕易改變. 接著讓你所依賴的程式去實做這一個 Interface.所以 SecretHelper 就變成如下:
clas SecretHelper : IWrtieSecret { public void Write(string data) { // code for writing secret data .... } public void WriteSecret(string data) { Write(data); } }
Interface 所定義的 WriteSecret() 去呼叫 Write().接著,DataWriterHelper 就會改成如下:
class DataWriterHelper { private IWriteSecret _secretWriter; public DataWriterHelper(IWriteSecret secretWriter) { _secretWriter = secretWriter; } public void WriteSecret(string data) { _secretWriter.WriteSecret(data); } }
經過這樣的修改,DataWriterHelper 便不再依賴 SecretHelper 了,而是變成依賴 IWriteSecret.因此,這樣做就把 class 和 class 之間的 dependency 切開,變成 class 依賴新的 interface. 這樣改變的想法來源是根據物件導向設計的 SOLID 原則.上面程式碼 IWirteSecret 的實作物件是透過 constructor injection 的方式傳來的,換句話說,DataWriterHelper 本身不參與 IWriterSecret 實作物件的建立過程,而是由外部的呼叫物件來決定要傳入那一個 IWriterSecret 的物件.
這樣的改變,將使得 DataWriterHelper 的測試力變得強一點,因為你可以傳入測試用的 IWrtieSecret 物件用在 DataWrtierHelper 的 unit test 或 integration test 的情境中.
0 意見:
張貼留言