續上集。接著要來進一步了解的是 DI 的實作技術,也就是注入相依物件的方式。這裡介紹的相依性注入方式,又稱為「窮人的 DI」(poor man’s DI),因為這些用法都與特定 DI 工具無關,亦即不使用任何現成的 DI 框架(例如 Unity、Autofac)。畢竟,DI 只是一組設計原則與模式,不依賴任何工具也能實現。
每個模式都描述了一個不斷發生在我們周遭的問題,然後描述該問題的核心解法,於是你便可以一再使用該解法,而無須對同樣的事情做兩次工。
—— Christopher Alexander. A Pattern Language.
除了第 1 章提到的 S.O.L.I.D. 設計原則,在運用 DI 技術時,也經常需要搭配一些設計模式(design patterns),例如 Factory Method(工廠方法)、Decorator(裝飾)、Composite(組合)、Adapter(轉換器)等等。基於後續章節討論的必要,本節將介紹幾個相關的設計模式。如需比較完整深入的介紹,可參考相關書籍,例如:《物件導向設計模式》、《深入淺出設計模式》、《重構-向範式前進》等等。
小引-電器與介面
日常生活中,四處可見電器用品,例如電視、微波爐、電腦等等。這些電器通常都有條電線,電線尾端是個插頭,而當我們要使用這些電器時,就把插頭插在牆壁或電源插座上,電器便能夠獲得所需之電力。一般情況下,沒有人會捨插座不用,而把電器的電源線固定焊在牆壁的電源插座。假使真這麼做,萬一有一天電視或電腦故障而需要維修,那可就麻煩了。
不只電源插座,電腦的 USB 插槽也一樣——它們都具備寬鬆耦合的特性。這裡的電源插座或 USB 插槽,對應到軟體世界裡的概念,便是介面。一個介面就等於是一份規格,而各家廠商所生產的各式各樣的電源插座或 USB 插槽,就是遵照其標準規格(介面)所實作出來的產品,或簡稱實作品。用軟體的術語來說,這些實作品就是類別——實作了特定介面的類別。
介面的威力即在於一旦訂出標準規格,各家廠商便可依照標準介面來製作各類產品。對使用者來說,好處則是享有多種選擇,因為他們不會被特定廠商的產品綁住;只要他們高興,隨時可以更換不同的產品,而且通常是隨插即用。在軟體的世界裡,介面也有同樣的好處:讓類別與類別之間保持寬鬆耦合,以便提供隨時抽換實作類別的彈性。
Null Object 模式
回到電源插座的例子。如果我們將電腦的電源線從插座上拔起,它們就只是彼此不再連接而已,電腦和插座並不會因此而著火或爆炸。但是在軟體程式的世界裡,若物件 A 會呼叫物件 B(物件 A 依賴物件 B),而當你將物件 B 移除,亦即物件 B 不存在時,程式就會發生 NullReferenceException 類型的錯誤。於是,我們常常會在程式裡面加入檢查物件參考是否為 null 的邏輯,例如:
if (anObject != null) anObject.DoSomething(); else DoSomethingElse();
如果在程式中一再重複寫這些檢查 null 的邏輯,程式碼便會膨脹,而且在解讀程式的主要邏輯時,常常得要跳過這些檢查邏輯,多少會形成閱讀程式碼的阻礙。針對此問題,我們可以設計一個空的、完全不做任何事的類別,然後在變數有可能是 null 的地方,讓它們指向那個空的物件。這種模式叫做 Null Object。
Null Object 的優點:可減少撰寫判斷物件參考是否為 null 的防錯邏輯。但前提是開發人員得知道有 Null Object 可用,否則還是會寫出多餘的防錯程式碼。
Null Object 類別通常要實作某個介面(或繼承自抽象類別),但實作程式碼完全沒做任何事,即所有方法都只是個空殼子,或僅提供無害的預設行為。以程式中常用的 logging(日誌)機制為例,我們可以將寫入日誌的操作定義成一個 ILogger 介面,然後依實際需要實作不同的 logging 類別,例如用來將日誌訊息輸出至 Console 的 ConsoleLogger。此外,考慮到應用程式有時候可能不需要紀錄任何訊息,我們可以實作一個 NullLogger 類別,當作 Null Object 使用。結構圖如下。
底下分別是 ILoger 介面以及 NullLogger 和 ConsoleLogger 類別的程式碼:
public interface ILogger { Log(string msg); } public class NullLogger : ILogger { public void Log(string msg) { // 不做任何事 } } public class ConsoleLogger : ILogger { public void Log(string msg) { Console.WriteLine(msg); } }
像底下這個函式,呼叫端只要傳入 ConsoleLogger 物件,日誌訊息就會輸出至 Console;而當呼叫端想要停止記錄日誌,便可傳入 NullLogger 物件。如此一來,就不用在每次寫入日誌訊息時都重複寫一遍檢查 logger 物件是否為 null 的防錯邏輯。
void DoSomething(ILogger logger) { logger.Log("開始執行 DoSomething 函式。"); .... }
Note: Null Object 本身並不需要「進化」成真正有做事的物件,因為它的存在就是為了提供一個完全不做任何事、不具任何意義的物件。
public class DecoratedLogger : ILogger { private ILogger logger; public DecoratedLogger(ILogger aLogger) { logger = aLogger; } public void Log(string msg) { logger.Log(DateTime.Now.ToString() + " - " + msg); } }
void DoSomething() { ILogger logger = new DecoratedLogger(new ConsoleLogger()); logger.Log("Hello, 裝飾模式!"); }
Dependency Injection 筆記 (3),布布扣,bubuko.com
原文:http://www.cnblogs.com/huanlin/p/3909207.html