介绍 (Introduction)
Dependency Injection and IoC can seem quite complex at first, but they are very easy to learn and understand.
依赖注入和IoC乍一看似乎很复杂,但是它们很容易学习和理解。
In this article, we will explain Dependency Injection and IoC containers by refactoring a very simple code sample in C#.
在本文中,我们将通过在C#中重构一个非常简单的代码示例来说明依赖注入和IoC容器。
要求 (Requirements)
Build an application that allows the user to see the available products and search products by name.
构建一个允许用户查看可用产品并按名称搜索产品的应用程序。
第一次尝试 (First Attempt)
We will start by creating a layered architecture. There are multiple benefits using a layered architecture but we are not going to list them in this article since we are focusing on Dependency Injection.
我们将从创建分层体系结构开始。 使用分层体系结构有多种好处,但是由于我们将重点放在依赖注入上,因此我们将不在本文中列出它们。
Below is the class diagram of the application:
下面是该应用程序的类图:
First and foremost, we will start by creating a Product
class:
首先,我们将首先创建一个Product
类:
-
public class Product { public Guid Id { get; set; } public string Name { get; set; } public string Description { get; set; } }
Then, we will create the Data Access layer:
然后,我们将创建数据访问层:
-
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(); } }
Then, we will create the business layer:
然后,我们将创建业务层:
-
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); } }
Finally, we will create the UI:
最后,我们将创建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(); } }
The code that we have written in the first attempt is working fine but there are few problems:
我们在第一次尝试中编写的代码可以正常工作,但是几乎没有问题:
- We cannot have three different teams working on each layer.
- The business layer is hard to extend because it relies on the implementation of the data access layer.
- The business layer is hard to maintain because it relies on the implementation of the data access layer.
- The source code is very hard to test.
第二次尝试 (Second Attempt)
The higher level object should not be dependent on the lower level object. Both have to be dependent on abstraction. So then what is the abstraction?
较高级别的对象不应依赖于较低级别的对象。 两者都必须依赖抽象。 那么,什么是抽象呢?
The abstraction is the definition of the functionality. In our case, the business layer is dependent on the data access layer to retrieve books. In C#, to implement abstraction, we use interfaces. An interface represents an abstraction of the functionality.
抽象是功能的定义。 在我们的例子中,业务层依赖于数据访问层来检索书籍。 在C#中,为了实现抽象,我们使用接口。 接口表示功能的抽象。
So let's create abstractions.
因此,让我们创建抽象。
Below is the abstraction of the data access layer:
下面是数据访问层的抽象:
-
public interface IProductDAL { IEnumerable<Product> GetProducts(); IEnumerable<Product> GetProducts(string name); }
We also need to update the data access layer:
我们还需要更新数据访问层:
public class ProductDAL : IProductDAL
We also need to update the business layer. Indeed, instead of being dependant on the implementation of the data access layer, we will update the business layer to be dependant on the abstraction of the data access layer:
我们还需要更新业务层。 实际上,我们将不依赖于数据访问层的实现,而是将业务层更新为依赖于数据访问层的抽象:
-
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); } }
We also have to create an abstraction of the business layer:
我们还必须创建业务层的抽象:
-
public interface IProductBL { IEnumerable<Product> GetProducts(); IEnumerable<Product> GetProducts(string name); }
We need to update the business layer too:
我们也需要更新业务层:
public class ProductBL : IProductBL
And finally, we have to update the UI:
最后,我们必须更新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(); } }
The code that we have done in the second attempt works but we are still dependant on the concrete implementation of the data access layer:
我们在第二次尝试中完成的代码可以工作,但是我们仍然依赖于数据访问层的具体实现:
-
public ProductBL() { _productDAL = new ProductDAL(); }
So, how to solve this? This is where the Dependency Injection pattern comes into play.
那么,如何解决呢? 这就是依赖注入模式起作用的地方。
最终尝试 (Final Attempt)
All the work we have done so far is not about Dependency Injection.
到目前为止,我们所做的所有工作都不是依赖注入。
In order for the business layer which is the higher level object to be dependent on the functionality of the lower level object without the concrete implementation, someone else has to create the class. Someone else has to provide the concrete implementation of the lower level object and that's what we call Dependency Injection. It literally means we're injecting the dependant object into the higher level object. One of the ways to implement Dependency Injection is to use Constructor Dependency Injection.
为了在没有具体实现的情况下使作为较高层对象的业务层依赖于较低层对象的功能,其他人必须创建该类。 其他人必须提供较低层对象的具体实现,这就是我们所说的依赖注入。 从字面上看,这意味着我们正在将依赖对象注入到更高级别的对象中。 实现依赖注入的方法之一是使用构造函数依赖注入。
So let's update the business layer:
因此,让我们更新业务层:
-
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); } }
The infrastructure has to provide the dependency to the implementation:
基础结构必须提供对实现的依赖:
-
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(); } }
The control of creating the data access layer is invereted into the infrastructure. This is also called Inversion of Control. Instead of creating the instance of the data access layer in the business layer, we are creating it inside of the infrastructure which is the Main
method. The Main
method will inject the instance into the business logic layer. So we are injecting the instance of the lower level object into the instance of the higher level object. So, that's called Dependency Injection.
创建数据访问层的控件已集成到基础架构中。 这也称为控制反转。 我们不是在业务层中创建数据访问层的实例,而是在作为Main
方法的基础结构内部创建它。 Main
方法将实例插入业务逻辑层。 因此,我们将低层对象的实例注入到高层对象的实例中。 因此,这称为依赖注入。
Now if we look at the code, we only have dependency on the abstraction of the data access layer in the business access layer which is the interface that the data access layer implements. Therefore, we are following the principles of both higher level objects and lower level objects are dependant on abstraction which is a contract between the higher level object and the lower level object.
现在,如果我们看一下代码,我们仅依赖于业务访问层中数据访问层的抽象,而业务访问层是数据访问层实现的接口。 因此,我们遵循上层对象和下层对象都依赖于抽象的原理,抽象是上层对象和下层对象之间的契约。
Now, we can have different teams working on different layers. We can have one team working on the data access layer, one team working on the business layer and one team working on the UI.
现在,我们可以有不同的团队在不同的层次上工作。 我们可以有一个团队在数据访问层上工作,一个团队在业务层上工作,一个团队在UI上工作。
And then it comes to the benefits of maintainability and extensibility. If we want for example to create a new data access layer for SQL Server, we only have to implemlent the abstraction of the data access layer and inject the instance in the infrastructure.
然后是可维护性和可扩展性的好处。 例如,如果我们想为SQL Server创建一个新的数据访问层,则只需破坏数据访问层的抽象并将实例注入基础结构中即可。
Finally, the source code is now testable. As we are using interfaces everywhere, we can easily provide another implementation in lower unit tests. This means that lower tests will be much easier to set up.
最后,源代码现在可以测试了。 由于我们在各处使用接口,因此我们可以轻松地在较低的单元测试中提供另一种实现。 这意味着较低的测试将更容易设置。
Now, let's test the business layer. We will be using xUnit for unit testing and Moq to mock the data access layer.
现在,让我们测试业务层。 我们将使用xUnit进行单元测试,并使用Moq模拟数据访问层。
Below are the unit tests of the business layer:
以下是业务层的单元测试:
-
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); } }
You can see that the unit tests were easy to set up using dependency injection.
您可以看到,使用依赖注入很容易设置单元测试。
IoC容器 (IoC Containers)
Containers are just something that helps implement Dependency Injection. Containers, generally implements three different functionalities:
容器只是有助于实现依赖注入的东西。 容器通常实现三种不同的功能:
- Register the mapping between the interface and the concrete implementation of the interface
- Create objects and resolve dependencies
- Dispose
Let's implement a simple container to register the mapping as well as creating objects.
让我们实现一个简单的容器来注册映射以及创建对象。
First of all, we need a data structure that stores the mapping. We'll choose Hashtable
. This data structure will store the mapping.
首先,我们需要一个存储映射的数据结构。 我们将选择Hashtable
。 该数据结构将存储映射。
First, we will initiate the Hashtable
in the constructor of the Container. Then, we will create a method RegisterTransient
to register the mapping. And finally, we will create a method Create
that will create objects:
首先,我们将在Container的构造函数中启动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); } }
And finally, we have to update the UI:
最后,我们必须更新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(); } }
Now, let's implement Resolve
method in the container. This method will resolve dependencies.
现在,让我们在容器中实现Resolve
方法。 此方法将解决依赖关系。
Below is Resolve
method:
下面是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) }); }
We can then use Resolve
method as follows in the UI:
然后,我们可以在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(); } }
In the above source code, the container creates an object of ProductBL
class using the container.Resolve<IProductBL>()
method. ProductBL
class is a dependency of IProductDAL
. So, container.Resolve<IProductBL>()
returns an object of ProductBL
class by automatically creating and injecting a ProductDAL
object in it. All this is behind the scene. ProductDAL
object is created and injected because we registered ProductDAL
type with IProductDAL
.
在上面的源代码中,容器使用container.Resolve<IProductBL>()
方法创建ProductBL
类的对象。 ProductBL
类是IProductDAL
的依赖IProductDAL
。 因此, container.Resolve<IProductBL>()
通过自动在其中创建和注入ProductDAL
对象来返回ProductBL
类的对象。 所有这些都在幕后。 因为我们向IProductDAL
注册了ProductDAL
类型, IProductDAL
创建并注入了ProductDAL
对象。
This is a very simple and basic IoC container to show you what's behind IoC containers. There are multiple IoC containers out there that you can use in your .NET source code.
这是一个非常简单且基本的IoC容器,向您展示IoC容器背后的内容。 您可以在.NET源代码中使用多个IoC容器。
That's it. I hope you enjoyed reading this article.
而已。 我希望您喜欢阅读本文。
历史 (History)
-
25th July, 2020: Initial version
2020年7月25 日 :初始版本
-
29th July, 2020: Added unit tests and
Resolve
method2020年7月29 日 :添加了单元测试和
Resolve
方法 -
30th July, 2020: Updated
Resolve
method2020年7月30 日 :更新了
Resolve
方法
原文链接:(2条消息) C#中的依赖注入和IoC容器_cunhan4654的博客-CSDN博客
标签:容器,layer,依赖,ProductBL,products,new,GetProducts,IoC,public From: https://www.cnblogs.com/ning-xiaowo/p/16876750.html