C#中的依賴注入和IoC容器

在本文中,咱們將經過用C#重構一個很是簡單的代碼示例來解釋依賴注入和IoC容器。 數據結構

簡介:

依賴注入和IoC乍一看可能至關複雜,但它們很是容易學習和理解。架構

在本文中,咱們將經過在C#中重構一個很是簡單的代碼示例來解釋依賴注入和IoC容器。函數

要求:

構建一個容許用戶查看可用產品並按名稱搜索產品的應用程序。單元測試

第一次嘗試:

咱們將從建立分層架構開始。使用分層架構有多個好處,但咱們不會在本文中列出它們,由於咱們關注的是依賴注入。
image
 下面是應用程序的類圖:
image
首先,咱們將從建立一個Product類開始:學習

public class Product
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

而後,咱們將建立數據訪問層:測試

public class ProductDAL
{
    private readonly List<Product> _products;

    public ProductDAL()
    {
        _products = new List<Product>
        {
            new Product { Id = Guid.NewGuid(), Name= "iPhone 9", 
                          Description = "iPhone 9 mobile phone" },
            new Product { Id = Guid.NewGuid(), Name= "iPhone X", 
                          Description = "iPhone X mobile phone" }
        };
    }

    public IEnumerable<Product> GetProducts()
    {
        return _products;
    }

    public IEnumerable<Product> GetProducts(string name)
    {
        return _products
            .Where(p => p.Name.Contains(name))
            .ToList();
    }
}

而後,咱們將建立業務層:ui

public class ProductBL
{
    private readonly ProductDAL _productDAL;

    public ProductBL()
    {
        _productDAL = new ProductDAL();
    }

    public IEnumerable<Product> GetProducts()
    {
        return _productDAL.GetProducts();
    }

    public IEnumerable<Product> GetProducts(string name)
    {
        return _productDAL.GetProducts(name);
    }
}

最後,咱們將建立UI:this

class Program
{
    static void Main(string[] args)
    {
        ProductBL productBL = new ProductBL();

        var products = productBL.GetProducts();

        foreach (var product in products)
        {
            Console.WriteLine(product.Name);
        }

        Console.ReadKey();
    }
}

咱們已經寫在第一次嘗試的代碼是良好的工做成果,但有幾個問題:spa

1.咱們不能讓三個不一樣的團隊在每一個層上工做。code

2.業務層很難擴展,由於它依賴於數據訪問層的實現。

3.業務層很難維護,由於它依賴於數據訪問層的實現。

4.源代碼很難測試。

第二次嘗試:

高級別對象不該該依賴於低級別對象。二者都必須依賴於抽象。那麼抽象概念是什麼呢?

抽象是功能的定義。在咱們的例子中,業務層依賴於數據訪問層來檢索圖書。在C#中,咱們使用接口實現抽象。接口表示功能的抽象。

讓咱們來建立抽象。

下面是數據訪問層的抽象:

public interface IProductDAL
{
    IEnumerable<Product> GetProducts();
    IEnumerable<Product> GetProducts(string name);
}

 咱們還須要更新數據訪問層:

public class ProductDAL : IProductDAL

咱們還須要更新業務層。實際上,咱們將更新業務層,使其依賴於數據訪問層的抽象,而不是依賴於數據訪問層的實現:

public class ProductBL
{
    private readonly IProductDAL _productDAL;

    public ProductBL()
    {
        _productDAL = new ProductDAL();
    }

    public IEnumerable<Product> GetProducts()
    {
        return _productDAL.GetProducts();
    }

    public IEnumerable<Product> GetProducts(string name)
    {
        return _productDAL.GetProducts(name);
    }
}

咱們還必須建立業務層的抽象:

public interface IProductBL
{
    IEnumerable<Product> GetProducts();
    IEnumerable<Product> GetProducts(string name);
}

咱們也須要更新業務層:

public class ProductBL : IProductBL

最終咱們須要更新UI:

class Program
{
    static void Main(string[] args)
    {
        IProductBL productBL = new ProductBL();

        var products = productBL.GetProducts();

        foreach (var product in products)
        {
            Console.WriteLine(product.Name);
        }

        Console.ReadKey();
    }
}

咱們在第二次嘗試中所作的代碼是有效的,但咱們仍然依賴於數據訪問層的具體實現:

public ProductBL()
{
    _productDAL = new ProductDAL();
}

那麼,如何解決呢?

這就是依賴注入模式發揮做用的地方。

最終嘗試

到目前爲止,咱們所作的工做都與依賴注入無關。

爲了使處在較高級別的的業務層依賴於較低級別對象的功能,而沒有具體的實現,必須由其餘人建立類。其餘人必須提供底層對象的具體實現,這就是咱們所說的依賴注入。它的字面意思是咱們將依賴對象注入到更高級別的對象中。實現依賴項注入的方法之一是使用構造函數進行依賴項注入。

讓咱們更新業務層:

public class ProductBL : IProductBL
{
    private readonly IProductDAL _productDAL;

    public ProductBL(IProductDAL productDAL)
    {
        _productDAL = productDAL;
    }

    public IEnumerable<Product> GetProducts()
    {
        return _productDAL.GetProducts();
    }

    public IEnumerable<Product> GetProducts(string name)
    {
        return _productDAL.GetProducts(name);
    }
}

 基礎設施必須提供對實現的依賴:

class Program
{
    static void Main(string[] args)
    {
        IProductBL productBL = new ProductBL(new ProductDAL());

        var products = productBL.GetProducts();

        foreach (var product in products)
        {
            Console.WriteLine(product.Name);
        }

        Console.ReadKey();
    }
}

建立數據訪問層的控制與基礎設施結合在一塊兒。這也稱爲控制反轉。咱們不是在業務層中建立數據訪問層的實例,而是在基礎設施的中建立它。 Main方法將把實例注入到業務邏輯層。所以,咱們將低層對象的實例注入到高層對象的實例中。

這叫作依賴注入。

如今,若是咱們看一下代碼,咱們只依賴於業務訪問層中數據訪問層的抽象,而業務訪問層是使用的是數據訪問層實現的接口。所以,咱們遵循了更高層次對象和更低層次對象都依賴於抽象的原則,抽象是更高層次對象和更低層次對象之間的契約。

如今,咱們可讓不一樣的團隊在不一樣的層上工做。咱們可讓一個團隊處理數據訪問層,一個團隊處理業務層,一個團隊處理UI。

接下來就顯示了可維護性和可擴展性的好處。例如,若是咱們想爲SQL Server建立一個新的數據訪問層,咱們只需實現數據訪問層的抽象並將實例注入基礎設施中。

最後,源代碼如今是可測試的了。由於咱們在任何地方都使用接口,因此咱們能夠很容易地在較低的單元測試中提供另外一個實現。這意味着較低的測試將更容易設置。

如今,讓咱們測試業務層。

咱們將使用xUnit進行單元測試,使用Moq模擬數據訪問層。

下面是業務層的單元測試:

public class ProductBLTest
{
    private readonly List<Product> _products = new List<Product>
    {
        new Product { Id = Guid.NewGuid(), Name= "iPhone 9", 
                      Description = "iPhone 9 mobile phone" },
        new Product { Id = Guid.NewGuid(), Name= "iPhone X", 
                      Description = "iPhone X mobile phone" }
    };
    private readonly ProductBL _productBL;

    public ProductBLTest()
    {
        var mockProductDAL = new Mock<IProductDAL>();
        mockProductDAL
            .Setup(dal => dal.GetProducts())
            .Returns(_products);
        mockProductDAL
            .Setup(dal => dal.GetProducts(It.IsAny<string>()))
            .Returns<string>(name => _products.Where(p => p.Name.Contains(name)).ToList());

        _productBL = new ProductBL(mockProductDAL.Object);
    }

    [Fact]
    public void GetProductsTest()
    {
        var products = _productBL.GetProducts();
        Assert.Equal(2, products.Count());
    }

    [Fact]
    public void SearchProductsTest()
    {
        var products = _productBL.GetProducts("X");
        Assert.Single(products);
    }
}

你能夠看到,使用依賴項注入很容易設置單元測試。

IoC容器

容器只是幫助實現依賴注入的東西。容器,一般實現三種不一樣的功能:

1.註冊接口和具體實現之間的映射

2.建立對象並解析依賴關係

3.釋放

讓咱們實現一個簡單的容器來註冊映射並建立對象。

首先,咱們須要一個存儲映射的數據結構。咱們將選擇Hashtable。該數據結構將存儲映射。

首先,咱們將在容器的構造函數中初始化Hashtable。而後,咱們將建立一個RegisterTransient方法來註冊映射。最後,咱們會建立一個建立對象的方法 Create :

public class Container
{
    private readonly Hashtable _registrations;

    public Container()
    {
        _registrations = new Hashtable();
    }

    public void RegisterTransient<TInterface, TImplementation>()
    {
        _registrations.Add(typeof(TInterface), typeof(TImplementation));
    }

    public TInterface Create<TInterface>()
    {
        var typeOfImpl = (Type)_registrations[typeof(TInterface)];
        if (typeOfImpl == null)
        {
            throw new ApplicationException($"Failed to resolve {typeof(TInterface).Name}");
        }
        return (TInterface)Activator.CreateInstance(typeOfImpl);
    }
}

最終,咱們會更新UI:

class Program
{
    static void Main(string[] args)
    {
        var container = new Container();
        container.RegisterTransient<IProductDAL, ProductDAL>();

        IProductBL productBL = new ProductBL(container.Create<IProductDAL>());
        var products = productBL.GetProducts();

        foreach (var product in products)
        {
            Console.WriteLine(product.Name);
        }

        Console.ReadKey();
    }
}

如今,讓咱們在容器中實現Resolve方法。此方法將解決依賴關係。

Resolve方法以下:

public T Resolve<T>()
{
    var ctor = ((Type)_registrations[typeof(T)]).GetConstructors()[0];
    var dep = ctor.GetParameters()[0].ParameterType;
    var mi = typeof(Container).GetMethod("Create");
    var gm = mi.MakeGenericMethod(dep);
    return (T)ctor.Invoke(new object[] { gm.Invoke(this, null) });
}

而後咱們能夠在UI中使用以下Resolve方法:

class Program
{
    static void Main(string[] args)
    {
        var container = new Container();
        container.RegisterTransient<IProductDAL, ProductDAL>();
        container.RegisterTransient<IProductBL, ProductBL>();

        var productBL = container.Resolve<IProductBL>();
        var products = productBL.GetProducts();

        foreach (var product in products)
        {
            Console.WriteLine(product.Name);
        }

        Console.ReadKey();
    }
}

在上面的源代碼中,容器使用container.Resolve<IProductBL>()方法建立ProductBL類的一個對象。ProductBL類是IProductDAL的一個依賴項。所以,container.Resolve<IProductBL>() 經過自動建立並在其中注入一個ProductDAL對象返回ProductBL類的一個對象。這一切都在幕後進行。建立和注入ProductDAL對象是由於咱們用IProductDAL註冊了ProductDAL類型。

這是一個很是簡單和基本的IoC容器,它向你展現了IoC容器背後的內容。就是這樣。我但願你喜歡閱讀這篇文章。

 歡迎關注個人公衆號,若是你有喜歡的外文技術文章,能夠經過公衆號留言推薦給我。
image

相關文章
相關標籤/搜索