續(xù) 上集 。 接著要來進一步了解的是 DI 的實現(xiàn)技術(shù),也就是注入相依對象的方式。這里介紹的依賴注入方式,又稱為「窮人的 DI」(poor man’s DI),因為這些用法都與特定 DI 工具無關(guān),亦即不使用任何現(xiàn)成的 DI 框架(例如 Unity、Autofac)。畢竟,DI 只是一組設計原則與模式,不依賴任何工具也能實現(xiàn)。
(本文摘自電子書:《 .NET?依賴注入 》)
?
設計模式梗概
每個模式都描述了一個不斷發(fā)生在我們周遭的問題,然后描述該問題的核心解法,于是你便可以一再使用該解法,而無須對同樣的事情做兩次工。
—— Christopher Alexander. A Pattern Language.
除了第 1 章提到的 S.O.L.I.D. 設計原則,在運用 DI 技術(shù)時,也經(jīng)常需要搭配一些設計模式(design patterns),例如 Factory Method(工廠方法)、Decorator(裝飾)、Composite(組合)、Adapter(轉(zhuǎn)換器)等等。基于后續(xù)章節(jié)討論的必要,本節(jié)將介紹幾個相關(guān)的設計模式。如需比較完整深入的介紹,可參考相關(guān)書籍,例如:《面向?qū)ο笤O計模式》、《深入淺出設計模式》、《重構(gòu)-向范式前進》等等。
小引-電器與接口
日常生活中,四處可見電器用品,例如電視、微波爐、計算機等等。這些電器通常都有條電線,電線尾端是個插頭,而當我們要使用這些電器時,就把插頭插在墻壁或電源插座上,電器便能夠獲得所需之電力。一般情況下,沒有人會舍插座不用,而把電器的電源線固定焊在墻壁的電源插座。假使真這么做,萬一有一天電視或計算機故障而需要維修,那可就麻煩了。

不只電源插座,計算機的 USB 插槽也一樣——它們都具備寬松耦合的特性。這里的電源插座或 USB 插槽,對應到軟件世界里的概念,便是接口。一個接口就等于是一份規(guī)格,而各家廠商所生產(chǎn)的各式各樣的電源插座或 USB 插槽,就是遵照其標準規(guī)格(接口)所實現(xiàn)出來的產(chǎn)品,或簡稱實現(xiàn)品。用軟件的術(shù)語來說,這些實現(xiàn)品就是類型——實現(xiàn)了特定接口的類型。
接口的威力即在于一旦訂出標準規(guī)格,各家廠商便可依照標準接口來制作各類產(chǎn)品。對使用者來說,好處則是享有多種選擇,因為他們不會被特定廠商的產(chǎn)品綁??;只要他們高興,隨時可以更換不同的產(chǎn)品,而且通常是即插即用。在軟件的世界里,接口也有同樣的好處:讓類型與類型之間保持寬松耦合,以便提供隨時抽換實現(xiàn)類型的彈性。
Null Object 模式
回到電源插座的例子。如果我們將計算機的電源線從插座上拔起,它們就只是彼此不再連接而已,計算機和插座并不會因此而著火或爆炸。但是在軟件程序的世界里,若對象 A 會調(diào)用對象 B(對象 A 依賴對象 B),而當你將對象 B 移除,亦即對象 B 不存在時,程序就會發(fā)生 NullReferenceException 類型的錯誤。于是,我們常常會在程序里面加入檢查對象參考是否為 null 的邏輯,例如:
if (anObject != null ) anObject.DoSomething(); else DoSomethingElse();
如果在程序中一再重復寫這些檢查 null 的邏輯,代碼便會膨脹,而且在解讀程序的主要邏輯時,常常得要跳過這些檢查邏輯,多少會形成閱讀代碼的阻礙。針對此問題,我們可以設計一個空的、完全不做任何事的類型,然后在變量有可能是 null 的地方,讓它們指向那個空的對象。這種模式叫做 Null Object。
Null Object 的優(yōu)點:可減少編寫判斷對象參考是否為 null 的防錯邏輯。但前提是開發(fā)人員得知道有 Null Object 可用,否則還是會寫出多余的防錯代碼。
Null Object 類型通常要實現(xiàn)某個接口(或繼承自抽象類型),但實現(xiàn)代碼完全沒做任何事,即所有方法都只是個空殼子,或僅提供無害的默認行為。以程序中常用的 logging(日志)機制為例,我們可以將寫入日志的操作定義成一個 ILogger 接口,然后依實際需要實現(xiàn)不同的 logging 類型,例如用來將日志訊息輸出至 Console 的 ConsoleLogger。此外,考慮到應用程序有時候可能不需要紀錄任何訊息,我們可以實現(xiàn)一個 NullLogger 類型,當作 Null Object 使用。結(jié)構(gòu)圖如下。

底下分別是 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); } }
?
像底下這個函式,調(diào)用端只要傳入 ConsoleLogger 對象,日志訊息就會輸出至 Console;而當調(diào)用端想要停止記錄日志,便可傳入 NullLogger 對象。如此一來,就不用在每次寫入日志訊息時都重復寫一遍檢查 logger 對象是否為 null 的防錯邏輯。
void DoSomething(ILogger logger) { logger.Log( "開始執(zhí)行 DoSomething 函式。 " ); .... }
?
Note: ?Null Object 本身并不需要「進化」成真正有做事的對象,因為它的存在就是為了提供一個完全不做任何事、不具任何意義的對象。
Decorator 模式

延續(xù)前面的 logging 范例,假設想要在每次輸出 log 訊息時額外加上當時的日期時間,而且前提是不可修改現(xiàn)有的 ILogger 和 ConsoleLogger 類型,該怎么做?
我們可以使用 Decorator 模式。作法為:設計一個新的類型,此類型不僅要實現(xiàn) ILogger 接口,而且還需要使用現(xiàn)有的 ConsoleLogger 對象來輸出 log 訊息。簡單起見,我就把這個類型命名為 DecoratedLogger。代碼如下:
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, 裝飾模式! " ); }
?
更多文章、技術(shù)交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
