目錄
介紹
這是有關依賴反轉原理,IoC容器和依賴注入的文章的繼續部分。在本文的上半部分,我解釋了什麼是依賴反轉原理及其好處。在本文的這一部分中,我將介紹控制反轉(IoC)和什麼是IoC容器。
以下是本文不同部分的清單:
- 第1部分:依賴倒置原則
- 第2部分:控制反轉和IoC容器(當前正在閱讀)
- 第3部分:自定義IoC容器
- 第4部分:具有生命週期選項的自定義IoC容器
- 第5部分:使用Microsoft Unity的依賴項注入(DI)
控制反轉(IoC)
什麼是控制反轉
許多人對控制反轉有不同的看法。您可以在不同的應用程序上下文中具有不同的含義。基本上,這是通過切換到另一個進行控制的位置來反轉對依賴性的控制的模式。
DIP認爲高級模塊不應該依賴於低級模塊,高級模塊和低級模塊都應該依賴於抽象。如果要防止在更改低層模塊時更改高層模塊,則需要反轉控件,以使低層模塊不會控制高層模塊所需的接口和對象的創建。IoC是提供這種抽象的模式。
在下面的示例(源自上一篇文章)中,我們通過將用於創建依賴項的代碼切換到服務定位器(以粗體字母標記)來反轉依賴項對象(讀取器和寫入器)的創建。
public class Copy
{
public void DoWork()
{
IReader reader = serviceLocator.GetReader();
IWriter writer = serviceLocator.GetWriter();
string data = reader.Read();
writer.Write(data);
}
}
這是控制反轉的例子之一。可以有多種方法來反轉控件。但是在當前的軟件開發趨勢中,有3種反轉控制的基本方法。
- 接口反轉
- 流反轉
- 創建反轉
接口反轉
這是一種非常常見的反轉。我已經在本文的第一部分中討論了一些有關接口反轉的內容。通過爲我們所有的底層模塊提供抽象,並不意味着我們已經實現了DIP。
例如,我們有兩個低級別的模塊,Car和Bicycle兩者都具有自己的抽象。
public interface ICar
{
void Run();
}
public class Car : ICar
{
public void Run()
{
....
}
}
public interface IBicycle
{
void Move();
}
public class Bicycle : IBicycle
{
public void Move()
{
....
}
}
在上面的示例中,儘管我們定義了抽象,但仍然存在問題。問題在於每個低級模塊都有其自己的抽象。因此,當高級模塊與抽象進行交互時,高級類必須對每個抽象進行不同的處理。對於上述實例,當高級類持有ICar實例時,它需要調用Run()而方法,而當它持有IBicycle實例時,它需要調用Move()方法。因此,在上述情況下,高級類應該具有有關低級模塊的實現的知識。
但是接口反轉認爲高級模塊應定義抽象,低級模塊應遵循該抽象。以下示例說明了這一點:
public interface IVehicle
{
void Move();
}
public class Car : IVehicle
{
public void Move()
{
....
}
}
public class Bicycle : IVehicle
{
public void Move()
{
....
}
}
在上面的示例中,Car和Bicycle類均實現由高級類定義的IVehicle interface。在這種情況下,高級類不應理會包含引用的實例。它只是藉助接口來調用Move()方法。
流反轉
流反轉討論了反轉執行流。例如,在早期的基於DOS的程序中,該程序順序讀取每個字段的值。在輸入第一個字段的值之前,您不能輸入第二個字段的值,然後再輸入第三個字段,依此類推。以下是用C#編寫的控制檯應用程序的示例。
static void Main(string[] args)
{
int nValue1, nValue2, nSum;
Console.Write("Enter value1 :");
nValue1 = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter value2 :");
nValue2 = Convert.ToInt32(Console.ReadLine());
nSum = nValue1 + nValue2;
Console.WriteLine(string.Format
("The sum of {0} and {1} is {2}", nValue1, nValue2, nSum));
Console.ReadKey();
}
以下是上述程序的輸出:
在這裏,您只能在輸入value1值 之後輸入值value2。因此,value2的輸入 取決於value1的輸入。
但是,如果您查看下面的GUI應用程序,則可以看到我們如何反轉流程以消除依賴關係。
在這裏,執行流程是反向的,並且現在兩者都是輸入,value1並且value2彼此獨立。
創建反轉
在創建反轉中,將依賴對象的創建從高級模塊轉換到其他位置。
public class Copy
{
public void DoWork()
{
IReader reader = serviceLocator.GetReader();
IWriter writer = serviceLocator.GetWriter();
string data = reader.Read();
writer.Write(data);
}
}
在上面的程序中,我們將IReader和IWriter對象的創建轉換爲serviceLocator。Object創造責任被賦予serviceLocator。而且Copy程序對實例沒有任何想法。因此,對於任何增加新的實現類的IReader和IWriter,也沒有必要修改的Copy程序。
IoC容器還可以使用其他方式來實現創建反轉。IoC容器包含每個抽象及其實現的配置設置。每當需要抽象實例時,IoC容器都會將其提供給請求者。
在圖1.a中,實現了接口反轉,但是仍然存在依賴關係,因此高級模塊和低級模塊之間存在緊密耦合。但是在圖1.b中,這種依賴性通過IoC容器的實現得以解決。
將依賴注入到高級模塊(消費者)的機制稱爲“依賴注入(DI)”。DI使用IoC容器來解決依賴關係。
依賴注入
如上所述,DI是將依賴(低級實例)注入高級模塊的機制,DI使用IoC容器來保存與依賴項有關的配置設置,並根據請求解析抽象。
在.NET中,有許多IoC容器提供了根據請求注入依賴項的工具。下面列出了一些最受歡迎的IoC容器:
- Unity容器
- Castle Windsor
- NInject
- 結構映射 (Structure Map)
在本文的下一部分中,我將解釋如何實現我們自己的自定義IoC容器,以及實現依賴注入以解決與基於分層的應用程序相關的許多問題的方法。
總結
在本文的這一部分中,我試圖解釋什麼是控制反轉及其如何解決緊密耦合。希望您發現這個主題很好並且易於理解。