标签:style blog http 使用 io strong 文件 ar
續上集,接著要說明如何運用 DI 來讓剛才的範例程式具備執行時期切換實作類別的能力。
入門範例—DI 版本
為了讓 AuthenticationService 類別能夠在執行時期才決定要使用 EmailService 還是 ShortMessageService 來發送驗證碼,我們必須對這些類別動點小手術,把它們之間原本緊密耦合的關係鬆開——或者說「解耦合」。有一個很有效的工具可以用來解耦合:介面(interface)。
說得更明白些,原本 AuthenticationService 是相依於特定實作類別來發送驗證碼(如 EmailService),現在我們要讓它相依於某個介面,而此介面會定義發送驗證碼的工作必須包含那些操作。由於介面只是一份規格,並未包含任何實作,故任何類別只要實作了這份規格,便能夠與 AuthenticationService 銜接,完成發送驗證碼的工作。有了中間這層介面,開發人員便能夠「針對介面、而非針對實作來撰寫程式。」(program to an interface, not an implementation)3,使應用程式中的各部元件保持「有點黏、又不會太黏」的適當距離,從而達成寬鬆耦合的目標。
提煉介面(Extract Interface)
開始動手修改吧!首先要對 EmailService 和 ShortMessageService 進行抽象化(abstraction),亦即將它們的共通特性抽離出來,放在一個介面中,使這些共通特性成為一份規格,然後再分別由具象類別來實作這份規格。以下程式碼是重構之後的結果,包含一個介面,兩個實作類別。我在個別的 Send 方法中使用 Console.WriteLine 方法來輸出不同的訊息字串,方便觀察實驗結果(此範例是個 Console 類型的應用程式專案)。
interface IMessageService
{
void Send(User user, string msg);
}
class EmailService : IMessageService
{
public void Send(User user, string msg)
{
// 寄送電子郵件給指定的 user (略)
Console.WriteLine("寄送電子郵件給使用者,訊息內容:" + msg);
}
}
class ShortMessageService : IMessageService
{
public void Send(User user, string msg)
{
// 發送簡訊給指定的 user (略)
Console.WriteLine("發送簡訊給使用者,訊息內容:" + msg);
}
}
看類別圖可能會更清楚些:
圖 1-2:抽離出共通介面之後的類別圖 |
介面抽離出來之後,如先前提過的,AuthenticationService 就可以依賴此介面,而不用再依賴特定實作類別。為了方便比對差異,我將修改前後的程式碼都一併列出來:
class AuthenticationService
{
// 原本是這樣:
private ShortMessageService msgService;
public AuthenticationService()
{
this.msgSevice = new ShortMessageService();
}
// 現在改成這樣:
private IMessageService msgService;
public AuthenticationService(IMessageService service)
{
this.msgService = service;
}
}
修改前後的差異如下:
控制反轉(IoC)
現在 AuthenticationService 已經不依賴特定實作了,而只依賴 IMessageService 介面。然而,介面只是規格,沒有實作,亦即我們不能這麼寫(無法通過編譯):
IMessageService msgService = new IMessageService();
那麼物件從何而來呢?答案是由外界透過 AuthenticationService 的建構函式傳進來。請注意這裡有個重要意涵:非 DI 版本的 AuthenticationService 類別使用 new 運算子來建立特定訊息服務的物件,並控制該物件的生命週期;DI 版本的 AuthenticationService 則將此控制權交給外層呼叫端(主程式)來負責——換言之,相依性被移出去了,「控制反轉了」。
最後要修改的是主程式(MainApp):
class MainApp
{
public void Login(string userId, string pwd, string messageServiceType)
{
IMessageService msgService = null;
// 用字串比對的方式來決定該建立哪一種訊息服務物件。
switch (messageServiceType)
{
case "EmailService":
msgService = new EmailService();
break;
case "ShortMessageService":
msgService = new ShortMessageService();
break;
default:
throw new ArgumentException("無效的訊息服務型別!");
}
var authService = new AuthenticationService(msgService); // 注入相依物件
if (authService.TwoFactorLogin(userId, pwd))
{
// 此處沒有變動,故省略.
}
}
}
圖 1-3:改成 DI 版本之後的類別相依關係圖 |
(未完待續)
Dependency Injection 筆記 (2),布布扣,bubuko.com
标签:style blog http 使用 io strong 文件 ar
原文地址:http://www.cnblogs.com/huanlin/p/dinet_2.html