依賴反轉原理,IoC容器和依賴注入:第3部分

目錄

介紹

背景

自定義IoC容器的工作方式

自定義IoC容器

高級模塊的使用者

編碼自定義IoC容器

步驟1:在Visual Studio中創建一個空白解決方案,並創建以下項目

步驟2:將以下代碼添加到Container.cs(DIP.MyIoCContainer項目)

步驟3:構建DIP.Abstractions項目

步驟4:開發DIP.HighLevelModule

步驟5:實現抽象(DIP.Implementation)

步驟6:最終開發實際上將使用HighLevelModule(DIP.Consumer)的消費者類

運行應用程序

總結


介紹

這是我的第三篇有關依賴反轉原理,IoC容器和依賴注入的文章。在本文的上半部分,我試圖解釋什麼是IoC容器。如果您沒有閱讀本文的前面部分,請使用下面的鏈接閱讀它們,以更好地瞭解DIPIoC和依賴注入概念的要求:

在本文中,我將解釋如何創建自定義IoC容器,以及如何將其用於遵守依賴反轉原理。

背景

有很多方法可以實現IoC容器。在本文中,我將解釋自定義IoC容器的開發,該容器的工作方式與Microsoft Unity Container的工作方式類似。在本文的這一部分中,我將解釋基本的實現,而在本文的後續部分中,將向其中添加更多功能,以便您可以對Microsoft Unity Container的工作有一個瞭解。

您應該具有反射的實用知識,才能理解本文中編寫的代碼。

自定義IoC容器的工作方式

下圖顯示了自定義IoC容器的工作方式。每個部分使用一條水平線分開。下圖描述的場景包括三個部分:

高級模塊

如果您還記得的話,我在本文的第1部分中給出的Copy示例充當高級模塊。DIP表示,高級模塊不應依賴於低級模塊的實現,而應公開一個抽象,低級模塊應遵循該抽象。

每當需要低級模塊實例時,它都會藉助容器。上圖中的以下語句返回低級模塊的實例。

IReader object = customContainer.Resolve<IReader>();

高級模塊不必理會那些實現IReader接口的類。同樣,我們不需要在包含高級模塊的項目中添加包含低級模塊的項目的引用。容器的責任是創建低級模塊(依賴)的實例並將其返回到高級模塊。

自定義IoC容器

自定義IoC容器有兩種主要方法,它們將在外部公開。那些是:

  • Register<TypeToResolve, ResolvedType>()
  • Resolve<TypeToResolve>()

它維護一個字典,其中TypeToResolveResolvedType的組合將使用Register()方法以KeyValuePair的形式存儲。

Resolve方法首先驗證TypeToResolve是否已在字典中註冊,如果已註冊,則它嘗試使用反射創建其對應的ResolvedType實例。

注意:可以通過多種方式實現自定義IoC容器。這種實現是方法之一。

高級模塊的使用者

基本上,這是使用高級模塊執行操作的地方。它可以是Windows/Web/控制檯應用程序。該應用程序應該瞭解低級實現。

對於我們的高級模塊提供的IReader抽象示例,有一個名爲KeyBoardReader的實現類。消費者的項目應參考包含低級模塊實現的項目。因此,只要在低級實現中添加了其他內容,使用者就可以更改(取決於使用者的實現),但是高級模塊不會更改,因爲它確實引用了低級模塊,並且不知道誰是IReader抽象的實現者。

編碼自定義IoC容器

步驟1:在Visual Studio中創建一個空白解決方案,並創建以下項目

  • DIP.Abstractions (類庫項目)
    • IReader.cs
    • IWriter.cs
  • DIP.HighLevelModule (類庫項目)
    • Copy.cs
  • DIP.MyIoCContainer (類庫項目)
    • Container.cs
  • DIP.Implementation (類庫項目)
    • KeyboardReader.cs
    • PrinterWriter.cs
  • DIP.Consumer (控制檯應用程序)
    • Program.cs

這裏,要注意的重要事項是項目引用(項目之間的依賴關係)。

  • DIP.Abstractions 項目沒有引用任何項目,因爲它獨立於一切。
  • DIP.HighLevelModule 該項目確實有兩個引用:
    • 引用DIP.Abstractions:依其uses Abstractions而定,並不取決於實現
    • 引用DIP.MyIoCContainer:由於它將使用容器來解決依賴關係
  • DIP.Implementations有一個引用DIP.Abstractions,因爲它會implement the abstractions
  • DIP.MyIoCContainer沒有引用任何項目,因爲它是一個庫並且不依賴任何項目
  • DIP.Consumer 引用了以下項目:
    • DIP.AbstractionsDIP.Implementation:必實現DI Registration.
    • DIP.MyIocContainer:它將用作容器,該容器將傳遞到DIP.HighLevelModule以解決依賴關係
    • DIP.HighLevelModule:它將使用DIP.HighLevelModule執行操作

注意:在上述項目依賴項中,您可以注意到DIP.HighLevelModuleDIP.Implementation之間沒有依賴項。因此,在DIP.implementation中添加或刪除類不會更改DIP.HighLevelModule中的任何內容。

步驟2:將以下代碼添加到Container.csDIP.MyIoCContainer項目)

public class Container
{
    private Dictionary<Type,Type> iocMap = new Dictionary<Type,Type>();

    public void Register<TypeToResolve,ResolvedType>()
    {
        if (iocMap.ContainsKey(typeof(TypeToResolve)))
        {
            throw new Exception(string.Format
            ("Type {0} already registered.", typeof(TypeToResolve).FullName));
        }
        iocMap.Add(typeof(TypeToResolve), typeof(ResolvedType));
    }

    public T Resolve<T>()
    {
        return (T)Resolve(typeof(T));
    }

    public object Resolve(Type typeToResolve)
    {
        // Find the registered type for typeToResolve
        if (!iocMap.ContainsKey(typeToResolve))
            throw new Exception(string.Format("Can't resolve {0}.
            Type is not registered.", typeToResolve.FullName));

        Type resolvedType = iocMap[typeToResolve];

        // Try to construct the object
        // Step-1: find the constructor
        // (ideally first constructor if multiple constructors present for the type)
        ConstructorInfo ctorInfo = resolvedType.GetConstructors().First();

        // Step-2: find the parameters for the constructor and try to resolve those
        List<parameterinfo> paramsInfo = ctorInfo.GetParameters().ToList();
        List<object> resolvedParams = new List<object>();
        foreach (ParameterInfo param in paramsInfo)
        {
            Type t = param.ParameterType;
            object res = Resolve(t);
            resolvedParams.Add(res);
        }

        // Step-3: using reflection invoke constructor to create the object
        object retObject = ctorInfo.Invoke(resolvedParams.ToArray());

        return retObject;
    }
}

 

  • iocMap

一個Dictionary<Type,Type>成員,它將保持已註冊TypeToResolve的列表和它對應的ResolvedType類型。

  • Register

用於獲取TypeToResolve類型語法實例的方法:void Register<TypeToResolve, ResolvedType>();

  • Resolve

用於獲取TypeToResolve類型語法實例的方法:<TypeToResolve> Resolve<TypeToResolve>();

步驟3:構建DIP.Abstractions項目

該項目包含了HighLevelModule用於執行動作的抽象(接口)。在我們的情況下,我們有兩個抽象:

IReader.cs

public interface IReader
{
    string Read();
}

IWriter.cs

public interface IWriter
{
    void Write(string data);
}

步驟4:開發DIP.HighLevelModule

該模塊將使用抽象來執行操作。在我們的例子中,我們有Copy.cs,它將從IReader複製到IWriter

public class Copy
{
    private Container _container;
    private IReader _reader;
    private IWriter _writer;

    public Copy(Container container)
    {
        _container = container;
        _reader = _container.Resolve<IReader>();
        _writer = _container.Resolve<IWriter>();
    }

    public void DoCopy()
    {
        string stData = _reader.Read();
        _writer.Write(stData);
    }

步驟5:實現抽象(DIP.Implementation

該項目包含在DIP.Abstractions中定義的抽象的實現。單個抽象可以有任意多個實現。例如,我們可以有KeyboardReaderFileReader等從IReader接口實現。

爲了使類易於理解,我定義了兩個類,KeyboardReader(實現IReader)和PrinterWriter(實現IWriter)。

注意:這並未實現鍵盤的實際讀數。我只是爲了說明IoC容器的用法而不是如何從鍵盤讀取而保持簡單。

KeyboardReader.cs

public class KeyboardReader:IReader
{
    public string Read()
    {
        return "Reading from \"Keyboard\"";
    }
}

PrinterWriter.cs

public class PrinterWriter:IWriter
{
    public void Write(string data)
    {
        Console.WriteLine(string.Format("Writing to \"Printer\": [{0}]", data));
    }
}

步驟6:最終開發實際上將使用HighLevelModuleDIP.Consumer)的消費者類

它是一個與用戶交互的控制檯應用程序。

Program.cs

class Program
{
    static void Main(string[] args)
    {
        Container container = new Container();
        DIRegistration(container);
        Copy copy = new Copy(container);

        copy.DoCopy();
        Console.Read();
    }

    static void DIRegistration(Container container)
    {
        container.Register<IReader,KeyboardReader>();
        container.Register<IWriter,PrinterWriter>();
    }
}

您會注意到它包含另一個名爲DIRegistration()的方法。它基本上進行依賴項的註冊。聲明container.Register<IReader, KeyboardReader>()寄存器映射IReaderKeyboardReader,所以,每當有需要實現IReader對象,一個KeyboardReader實例將被返回。

注意:有許多方法可以註冊依賴項。我們還可以使用配置文件來實現DI註冊。

運行應用程序

如果運行開發的應用程序,將獲得以下輸出:

在這裏,您可以擴展實現而無需更改高級程序。您唯一需要做的就是更改註冊。

總結

在本文的這一部分,我試圖解釋如何開發簡單的IoC容器。希望您發現這個主題很好並且易於理解。在下一章中,我將介紹一些高級技術,以使自定義IoC容器更加現實和有用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章