目錄
介紹
在處理WPF應用程序時,我遇到過諸如Unity容器,IoC,依賴注入之類的術語。當時,我很迷茫,想着這一切的需要。但後來,當我逐漸瞭解到它的好處時,我意識到它的實際需要。
在本文中,我將嘗試解釋DI和IoC的需求和用法。基本上,本文分爲五個部分:
- 第1部分:依賴反轉原理(當前正在閱讀)
- 第2部分:控制反轉和IoC容器
- 第3部分:自定義IoC容器
- 第4部分:具有生命週期選項的自定義IoC容器
- 第5部分:使用Microsoft Unity的依賴項注入(DI)
本文的這一部分是關於依賴倒置原理的。希望您會發現本文易於理解和實現。
先決條件
最好對以下項一無所知:
- 開閉原則
- 接口隔離原理
依賴倒置原則(DIP)
DIP是SOLID原則之一,由Robert Martin C.先生在1992年提出。
- S —— 單一責任原則
- O—— 開/關原理
- L —— Liskov替代原理
- I ——接口隔離原理
- D——依賴反轉原理
根據羅伯特·馬丁(C. Robert Martin)的依賴倒置原則:
- 高級模塊不應依賴於低級模塊。兩者都應依賴抽象。
- 抽象不應依賴細節。細節應依賴於抽象。
DIP是指將常規依賴性從高級模塊轉換爲低級模塊。
Bob Martin Peper期刊的示例:
在圖1中,一個複製程序(高級模塊)從鍵盤讀取並寫入打印機。這裏的複製程序取決於Read Keyboard和Write Printer並緊密耦合。
public class Copy
{
public void DoWork()
{
ReadKeyboard reader = new ReadKeyboard();
WritePrinter writer = new WritePrinter();
string data = reader.ReadFromKeyboard();
writer.WriteToPrinter(data);
}
}
在我們要求向程序中添加更多讀取器或寫入器之前,此實現似乎非常好。在這種情況下,我們需要更改複製程序以適應新的讀者和作家,並且需要編寫條件語句,該條件語句將根據用途選擇讀者和作家,這違反了面向對象設計的“打開/關閉”原理。
例如,我們要擴展複印程序(參見圖1.b),該程序也可以從掃描儀讀取並寫入閃存盤。在這種情況下,我們需要修改複製程序:
public class Copy
{
public void DoWork()
{
string data;
switch (readerType)
{
case "keyboard":
ReadKeyboard reader = new ReadKeyboard();
data = reader.ReadFromKeyboard();
break;
case "scanner":
ReadScanner reader2 = new ReadScanner();
data = reader2.ReadFromScanner();
break;
}
switch (writerType)
{
case "printer":
WritePrinter writer = new WritePrinter();
writer.WriteToPrinter(data);
break;
case "flashdisk":
WriteFlashDisk writer2 = new WriteFlashDisk();
writer2.WriteToFlashDisk(data);
break;
}
}
}
同樣,如果您繼續添加更多讀取器或寫入器,則我們需要更改複製程序的實現,因爲複製程序取決於讀取器和寫入器的實現。
爲了解決這個問題,我們可以修改copy程序,使其依賴於抽象而不是依賴於實現。下圖說明了反轉依賴關係。
在上圖中,Copy程序依賴於兩個抽象,IReader和IWriter來執行。只要底層組件能夠對抽象進行確認,copy程序就可以從這些組件中讀取內容。
例如,在上圖中,ReadKeyboard實現IReader接口和WritePrinter實現IWriter,因此使用IReader和IWriter接口複製程序可以執行復制操作。因此,如果我們需要添加更多低級組件(如掃描儀和閃存盤),則可以通過從掃描儀和閃存盤來實現。以下代碼說明了這種情況:
public interface IReader
{
string Read();
}
public interface IWriter
{
void Write(string data);
}
public class ReadKeyboard : IReader
{
public string Read()
{
// code to read from keyboard and return as string
}
}
public class ReadScanner : IReader
{
public string Read()
{
// code to read from scanner and return as string
}
}
public class WritePrinter : IWriter
{
public void Write(string data)
{
// code to write to the printer
}
}
public class WriteFlashDisk : IWriter
{
public void Write(string data)
{
// code to write to the flash disk
}
}
public class Copy
{
private string _readerType;
private string _writerType;
public Copy(string readerType, string writerType)
{
_readerType = readerType;
_writerType = writerType;
}
public void DoWork()
{
IReader reader;
IWriter writer;
string data;
switch (readerType)
{
case "keyboard":
reader = new ReadKeyboard();
break;
case "scanner":
reader = new ReadScanner();
break;
}
switch (writerType)
{
case "printer":
writer = new WritePrinter();
break;
case "flashdisk":
writer = new WriteFlashDisk();
break;
}
data = reader.Read();
writer.Write(data);
}
}
在這種情況下,細節依賴於抽象,但高級類仍依賴於低級模塊。在實例化高級模塊範圍內的低級模塊對象時,高級模塊仍然需要在添加新的低級組件時進行修改,這不能完全滿足DIP。
爲了刪除依賴關係,我們需要在高級模塊之外創建依賴關係對象(低級組件),並且應該有某種機制將該依賴關係對象傳遞給依賴模塊。
現在出現了一個新問題,即如何實現依賴倒置。
上述問題的答案之一可以是控制反轉(IoC)。考慮以下代碼段:
public class Copy
{
public void DoWork()
{
IReader reader = serviceLocator.GetReader();
IWriter writer = serviceLocator.GetWriter();
string data = reader.Read();
writer.Write(data);
}
}
高亮顯示的代碼替換了實例化讀取器和寫入器對象的邏輯。在這裏,我們將控件創建從Copy程序(高級模塊)轉換爲服務定位器。因此,copy無需通過添加/刪除低級模塊來更改程序。
依賴注入是實現IoC的機制之一。在本文的下一部分中,我將介紹什麼是控制反轉(IoC),以及使用不同機制(依賴注入(DI)是實現之一)實現依賴反轉原理的方法。
總結
在本文的這一部分中,我已經解釋了依賴性反轉原理(DIP)及其在實時場景中的需求。
在本文的後面,我將解釋控制反轉(IoC)和依賴注入(DI)。