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

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

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

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