详解C#中的依赖注入和IoC容器
作者:码农驿站 发布时间:2023-03-11 09:05:19
在本文中,我们将通过用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();
}
}
然后,我们将创建业务层:
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(), 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容器背后的内容。就是这样。我希望你喜欢阅读这篇文章。
来源:https://segmentfault.com/a/1190000038709708


猜你喜欢
- TCP和UDP在网络传输中非常重要,在Android开发中同样重要。首先我们来看一下什么是TCP和UDP。什么是TCP?TCP:Transm
- WORD: import org.apache.lucene.document.Document; import org.apache.lu
- 1、普通用户与系统管理员用户的权限要有严格的区分。如果一个普通用户在使用查询语句中嵌入另一个Drop Table语句,那么是否允许
- 最近学习了 C#实现文件上传与下载,现在分享给大家。1、C#文件上传创建MyUpload.htm页面,用于测试<form name=&
- 1.登录腾讯云点击登录选择浏览器登录。输入用户名 按回车键 然后输入 密码。2.安装java环境直接命令:yum -y install ja
- 今天是解决报错的一天,首先在操作Springboot中的时候,有些朋友的yml显示的不是绿叶的图标,或者是配置了之后不生效的问题。第一个解决
- 在项目中,时常会有异步调用的需求web.xml配置<servlet> <description>spri
- JUC包(java.util.concurrent)中提供了对定时任务的支持,即ScheduledExecutorService接口。本文对
- 本文实例为大家分享了java pdf加水印的具体代码,供大家参考,具体内容如下引入依赖<dependency> <grou
- Android刚兴起的时候,着实让一些小众软件火了一把,切水果,Tom猫,吹裙子就是其中的代表,当然还有实用性很强的关机重启软件,我们去百度
- 前言本文基于itext7实现pdf加水印和合并的操作。实际上在我们实际项目应用中,对于pdf的操作也是比较常见的,我上一个项目中就有将结果转
- 自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性。例子如下:1
- docx4j变量替换的问题最近工作上需要自己完成word文档变量替换的问题把里面的变量给替换成数据库里的值,但是由于在word文档渲染成xm
- 教你如何用C#制作文字转换成声音程序在System.Speech命名空间下,SpeechSynthesizer类可以把文字读出来,一起来玩下
- 动态内存管理为什么存在动态内存分配我们到现在为止掌握的是什么样的内存开辟方式呢//创建一个变量int val = 20; &n
- 模块调用之后,记录模块的相关日志,看似简单,其实暗藏玄机。1.简述模块日志的实现方式大致有三种:AOP + 自定义注解实现输出指定格式日志
- 开发中最让人头疼的是应用突然 * ,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误
- 本文实例讲述了使用SAX来解析XML。通常来说在Android里面可以使用SAX和DOM,DOM需要把整个XML文件读入内存再解析,比较消耗
- Android ListView与ScrollView冲突的解决方法总结众所周知ListView与ScrollView都具有滚动能力,对于这
- android root权限破解分析许多机友新购来的Android机器没有破解过Root权限,无法使用一些需要高权限的软件,以及进行一些高权