在本文中,咱们将经过用C#重构一个很是简单的代码示例来解释依赖注入和IoC容器。 数据结构
简介:
依赖注入和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(); } }
而后,咱们将建立业务层: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容器背后的内容。就是这样。我但愿你喜欢阅读这篇文章。
欢迎关注个人公众号,若是你有喜欢的外文技术文章,能够经过公众号留言推荐给我。