詳解C#中的依賴注入和IoC容器
在本文中,我們將通過用C#重構(gòu)一個非常簡單的代碼示例來解釋依賴注入和IoC容器。
簡介:
依賴注入和IoC乍一看可能相當(dāng)復(fù)雜,但它們非常容易學(xué)習(xí)和理解。
在本文中,我們將通過在C#中重構(gòu)一個非常簡單的代碼示例來解釋依賴注入和IoC容器。
要求:
構(gòu)建一個允許用戶查看可用產(chǎn)品并按名稱搜索產(chǎn)品的應(yīng)用程序。
第一次嘗試:
我們將從創(chuàng)建分層架構(gòu)開始。使用分層架構(gòu)有多個好處,但我們不會在本文中列出它們,因?yàn)槲覀冴P(guān)注的是依賴注入。
下面是應(yīng)用程序的類圖:
首先,我們將從創(chuàng)建一個Product類開始:
public class Product { public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } }
然后,我們將創(chuàng)建數(shù)據(jù)訪問層:
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(); } }
然后,我們將創(chuàng)建業(yè)務(wù)層:
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); } }
最后,我們將創(chuàng)建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(); } }
我們已經(jīng)寫在第一次嘗試的代碼是良好的工作成果,但有幾個問題:
1.我們不能讓三個不同的團(tuán)隊(duì)在每個層上工作。
2.業(yè)務(wù)層很難擴(kuò)展,因?yàn)樗蕾囉跀?shù)據(jù)訪問層的實(shí)現(xiàn)。
3.業(yè)務(wù)層很難維護(hù),因?yàn)樗蕾囉跀?shù)據(jù)訪問層的實(shí)現(xiàn)。
4.源代碼很難測試。
第二次嘗試:
高級別對象不應(yīng)該依賴于低級別對象。兩者都必須依賴于抽象。那么抽象概念是什么呢?
抽象是功能的定義。在我們的例子中,業(yè)務(wù)層依賴于數(shù)據(jù)訪問層來檢索圖書。在C#中,我們使用接口實(shí)現(xiàn)抽象。接口表示功能的抽象。
讓我們來創(chuàng)建抽象。
下面是數(shù)據(jù)訪問層的抽象:
public interface IProductDAL { IEnumerable<Product> GetProducts(); IEnumerable<Product> GetProducts(string name); }
我們還需要更新數(shù)據(jù)訪問層:
public class ProductDAL : IProductDAL
我們還需要更新業(yè)務(wù)層。實(shí)際上,我們將更新業(yè)務(wù)層,使其依賴于數(shù)據(jù)訪問層的抽象,而不是依賴于數(shù)據(jù)訪問層的實(shí)現(xiàn):
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); } }
我們還必須創(chuàng)建業(yè)務(wù)層的抽象:
public interface IProductBL { IEnumerable<Product> GetProducts(); IEnumerable<Product> GetProducts(string name); }
我們也需要更新業(yè)務(wù)層:
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(); } }
我們在第二次嘗試中所做的代碼是有效的,但我們?nèi)匀灰蕾囉跀?shù)據(jù)訪問層的具體實(shí)現(xiàn):
public ProductBL() { _productDAL = new ProductDAL(); }
那么,如何解決呢?
這就是依賴注入模式發(fā)揮作用的地方。
最終嘗試
到目前為止,我們所做的工作都與依賴注入無關(guān)。
為了使處在較高級別的的業(yè)務(wù)層依賴于較低級別對象的功能,而沒有具體的實(shí)現(xiàn),必須由其他人創(chuàng)建類。其他人必須提供底層對象的具體實(shí)現(xiàn),這就是我們所說的依賴注入。它的字面意思是我們將依賴對象注入到更高級別的對象中。實(shí)現(xiàn)依賴項(xiàng)注入的方法之一是使用構(gòu)造函數(shù)進(jìn)行依賴項(xiàng)注入。
讓我們更新業(yè)務(wù)層:
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); } }
基礎(chǔ)設(shè)施必須提供對實(shí)現(xiàn)的依賴:
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(); } }
創(chuàng)建數(shù)據(jù)訪問層的控制與基礎(chǔ)設(shè)施結(jié)合在一起。這也稱為控制反轉(zhuǎn)。我們不是在業(yè)務(wù)層中創(chuàng)建數(shù)據(jù)訪問層的實(shí)例,而是在基礎(chǔ)設(shè)施的中創(chuàng)建它。 Main方法將把實(shí)例注入到業(yè)務(wù)邏輯層。因此,我們將低層對象的實(shí)例注入到高層對象的實(shí)例中。
這叫做依賴注入。
現(xiàn)在,如果我們看一下代碼,我們只依賴于業(yè)務(wù)訪問層中數(shù)據(jù)訪問層的抽象,而業(yè)務(wù)訪問層是使用的是數(shù)據(jù)訪問層實(shí)現(xiàn)的接口。因此,我們遵循了更高層次對象和更低層次對象都依賴于抽象的原則,抽象是更高層次對象和更低層次對象之間的契約。
現(xiàn)在,我們可以讓不同的團(tuán)隊(duì)在不同的層上工作。我們可以讓一個團(tuán)隊(duì)處理數(shù)據(jù)訪問層,一個團(tuán)隊(duì)處理業(yè)務(wù)層,一個團(tuán)隊(duì)處理UI。
接下來就顯示了可維護(hù)性和可擴(kuò)展性的好處。例如,如果我們想為SQL Server創(chuàng)建一個新的數(shù)據(jù)訪問層,我們只需實(shí)現(xiàn)數(shù)據(jù)訪問層的抽象并將實(shí)例注入基礎(chǔ)設(shè)施中。
最后,源代碼現(xiàn)在是可測試的了。因?yàn)槲覀冊谌魏蔚胤蕉际褂媒涌?,所以我們可以很容易地在較低的單元測試中提供另一個實(shí)現(xiàn)。這意味著較低的測試將更容易設(shè)置。
現(xiàn)在,讓我們測試業(yè)務(wù)層。
我們將使用xUnit進(jìn)行單元測試,使用Moq模擬數(shù)據(jù)訪問層。
下面是業(yè)務(wù)層的單元測試:
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); } }
你可以看到,使用依賴項(xiàng)注入很容易設(shè)置單元測試。
IoC容器
容器只是幫助實(shí)現(xiàn)依賴注入的東西。容器,通常實(shí)現(xiàn)三種不同的功能:
1.注冊接口和具體實(shí)現(xiàn)之間的映射
2.創(chuàng)建對象并解析依賴關(guān)系
3.釋放
讓我們實(shí)現(xiàn)一個簡單的容器來注冊映射并創(chuàng)建對象。
首先,我們需要一個存儲映射的數(shù)據(jù)結(jié)構(gòu)。我們將選擇Hashtable。該數(shù)據(jù)結(jié)構(gòu)將存儲映射。
首先,我們將在容器的構(gòu)造函數(shù)中初始化Hashtable。然后,我們將創(chuàng)建一個RegisterTransient方法來注冊映射。最后,我們會創(chuàng)建一個創(chuàng)建對象的方法 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(); } }
現(xiàn)在,讓我們在容器中實(shí)現(xiàn)Resolve方法。此方法將解決依賴關(guān)系。
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>()方法創(chuàng)建ProductBL類的一個對象。ProductBL類是IProductDAL的一個依賴項(xiàng)。因此,container.Resolve<IProductBL>() 通過自動創(chuàng)建并在其中注入一個ProductDAL對象返回ProductBL類的一個對象。這一切都在幕后進(jìn)行。創(chuàng)建和注入ProductDAL對象是因?yàn)槲覀冇肐ProductDAL注冊了ProductDAL類型。
這是一個非常簡單和基本的IoC容器,它向你展示了IoC容器背后的內(nèi)容。就是這樣。我希望你喜歡閱讀這篇文章。
以上就是詳解C#中的依賴注入和IoC容器的詳細(xì)內(nèi)容,更多關(guān)于C# 依賴注入和IoC容器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中接口的顯式實(shí)現(xiàn)與隱式實(shí)現(xiàn)及其相關(guān)應(yīng)用案例詳解
最近在學(xué)習(xí)演化一款游戲項(xiàng)目框架時候,框架作者巧妙使用接口中方法的顯式實(shí)現(xiàn)來變相對接口中方法進(jìn)行“密封”,增加實(shí)現(xiàn)接口的類訪問方法的“成本”,這篇文章主要介紹了C#中接口的顯式實(shí)現(xiàn)與隱式實(shí)現(xiàn)及其相關(guān)應(yīng)用案例,需要的朋友可以參考下2024-05-05C#使用ScrapySharp快速從網(wǎng)頁采集數(shù)據(jù)
這篇文章介紹了使用ScrapySharp快速從網(wǎng)頁采集數(shù)據(jù)的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06C#實(shí)現(xiàn)文件壓縮與解壓的方法示例【ZIP格式】
這篇文章主要介紹了C#實(shí)現(xiàn)文件壓縮與解壓的方法,結(jié)合具體實(shí)例形式分析了C#針對文件進(jìn)行zip格式壓縮與解壓縮的相關(guān)操作技巧,需要的朋友可以參考下2017-06-06