本文示例代碼,均採用 .NET 6,具體的代碼可以在這個倉庫 Articles.DI 中獲取。
讓我們看這麼一段簡單的代碼:
// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService1
public class MessageWriter {
public void Write(string message) => Console.WriteLine(message);
}
public class Worker : BackgroundService {
private readonly MessageWriter _msgWriter = new MessageWriter();
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
while (!stoppingToken.IsCancellationRequested) {
_msgWriter.Write($"Worker running at: {DateTime.Now}");
await Task.Delay(1000, stoppingToken);
}
}
}
在上面的代碼中,Worker
類包含有一個MessageWriter
類的實例成員,這個類創建並直接依賴於MessageWriter
類。這樣的代碼有一些問題:
- 如果
_msgWriter
變量要替換成別種類型的實現,需要修改Worker
類; - 如果
MessageWriter
類具有其他依賴項,則必須在Worker
類中進行配置; - 難以對
Worker
類進行單元測試。
如果有一個統一的管理者,負責MessageWriter
類型的實例化,然後將其傳遞給有需求的類,就能做到這兩個類之間的依賴解除,減少了硬編碼。而這,就是依賴注入。
依賴注入和控制反轉#
依賴注入(英文 Dependency Injection,簡稱 DI),是一種在類及其依賴項之間實現控制反轉(英文 Inversion of Control,簡稱 IoC)的技術。
而控制反轉,是面向對象編程中的一種設計原則,可以用來減少程序代碼間的耦合程度。除了依賴注入外,還有一種實現控制反轉的方法,叫做 “依賴查找”。
大多數程序都由多個類相互引用,彼此依賴合作以實現業務邏輯,這使得每個對象都需要獲取其他服務的引用。如果這個獲取過程需要靠對象自己實現,那麼這將導致類之間高度耦合,難以維護和調試。
傳統應用中,如果 A 服務要獲取 B 服務,則需要在自己的代碼塊中實例化。而如果使用依賴注入,A 服務只需聲明所需的服務,由外部容器來負責生成實例,A 服務只需接收即可。整個過程從主動變成了被動,反轉了對象的獲取過程,即實現了控制反轉。
下面兩張圖展示了兩種依賴項關係圖:
直接依賴項關係:類 A 調用類 B 的方法,類 B 調用類 C 的方法,則在編譯時,類 A 依賴於類 B,類 B 依賴於類 C。
應用依賴反轉後,類 A 引用的是接口 IB 的方法,不再直接依賴於類 B。在編譯時,則是類 A 和類 B 引用了接口 IB,和前面的直接依賴相比,反轉了依賴項。而類 B 和類 C 之間也是相似的。
這張圖,生動形象的解釋了控制反轉是如何解耦服務之間的依賴。
參考鏈接#
依賴關係反轉 - 現代 ASP.NET Web 應用程序電子書
Inversion of Control Containers and the Dependency Injection pattern