详解C#中的依赖注入和IoC容器
在本文中,我们将通过用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容器背后的内容。就是这样。我希望你喜欢阅读这篇文章。
以上就是详解c#中的依赖注入和ioc容器的详细内容,更多关于c# 依赖注入和ioc容器的资料请关注其它相关文章!
下一篇: 张飞与马超两人联手,为什么还斗不过曹洪?