1. 程式人生 > 程式設計 >詳解C#中的依賴注入和IoC容器

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

在本文中,我們將通過用C#重構一個非常簡單的程式碼示例來解釋依賴注入和IoC容器。

簡介:

依賴注入和IoC乍一看可能相當複雜,但它們非常容易學習和理解。

在本文中,我們將通過在C#中重構一個非常簡單的程式碼示例來解釋依賴注入和IoC容器。

要求:

構建一個允許使用者檢視可用產品並按名稱搜尋產品的應用程式。

第一次嘗試:

我們將從建立分層架構開始。使用分層架構有多個好處,但我們不會在本文中列出它們,因為我們關注的是依賴注入。

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

下面是應用程式的類圖:

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

首先,我們將從建立一個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();
  }
}

然後,我們將建立業務層:

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:

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();
  }
}

我們已經寫在第一次嘗試的程式碼是良好的工作成果,但有幾個問題:

1.我們不能讓三個不同的團隊在每個層上工作。

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(),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容器背後的內容。就是這樣。我希望你喜歡閱讀這篇文章。

以上就是詳解C#中的依賴注入和IoC容器的詳細內容,更多關於C# 依賴注入和IoC容器的資料請關注我們其它相關文章!