[ASP.NET Core 3框架揭秘] 依赖注入:依赖注入模式

IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在前面介绍的模板方法、工厂方法和抽象工厂,接下来我们介绍一种更有价值的IoC模式:依赖注入(DI:Dependency Injection)。

一、由容器提供对象

和前面介绍的工厂方法和抽象工厂模式一样,依赖注入是一种“对象提供型”的设计模式,在这里我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在一个采用依赖注入的应用中,我们定义某个类型的时候,只需要直接将它依赖的服务采用相应的方式注入进来就可以了。

在应用启动的时候,我们会对所需的服务进行全局注册。一般来说,服务大都是针对实现的接口或者继承的抽象类进行注册的,服务注册信息的帮助我们在后续消费过程中提供对应的服务实例。按照“好莱坞法则”,应用只需要定义并注册好所需的服务,服务实例的提供则完全交给框架来完成,框架则会利用一个独立的“容器(Container)”来提供所需的每一个服务实例。

我们将这个被框架用来提供服务的容器称为“依赖注入容器”,也有很多人将其称为“IoC容器”,根据前面针对IoC的介绍,我不认为后者是一个合理的称谓。依赖注入容器之所以能够按照我们希望的方式来提供所需的服务是因为该容器是根据服务注册信息来创建的,服务注册了包含提供所需服务实例的所有信息。

举个简单的例子,我们创建一个名为Cat的依赖注入容器类型,那么我们可以调用如下这个扩展方法GetService<T>从某个Cat对象中获取指定类型的服务对象。我之所以将其命名为Cat,源于我们大家都非常熟悉的一个卡通形象“机器猫(哆啦A梦)”。机器猫的那个四次元口袋就是一个理想的依赖注入容器,大熊只需要告诉哆啦A梦相应的需求,它就能从这个口袋中得到相应的法宝。依赖注入容器亦是如此,服务消费者只需要告诉容器所需服务的类型(一般是一个服务接口或者抽象服务类),就能得到与之匹配的服务实例。

public static class CatExtensions
{
public static T GetService<T>(this Cat cat);
}

对于我们演示的MVC框架来说,我们在前面分别采用不同的设计模式对框架的核心类型MvcEngine进行了“改造”,现在我们采用依赖注入的方式,并利用上述的这个Cat容器按照如下的方式对其进行重新实现,我们会发现MvcEngine变得异常简洁而清晰。

public class MvcEngine
{
public Cat Cat { get; }
public MvcEngine(Cat cat) => Cat = cat;

public async Task StartAsync(Uri address)
{
var listener = Cat.GetService<IWebListener>();
var activator = Cat.GetService<IControllerActivator>();
var executor = Cat.GetService<IControllerExecutor>();
var renderer = Cat.GetService<IViewRenderer>();

await listener.ListenAsync(address);
while (true)
{
var httpContext = await listener.ReceiveAsync();
var controller = await activator.CreateControllerAsync(httpContext);
try
{
var view = await executor.ExecuteAsync(controller, httpContext);
await renderer.RenderAsync(view, httpContext);
}
finally
{
await activator.ReleaseAsync(controller);
}
}
}
}

依赖注入体现了一种最为直接的服务消费方式,消费者只需要告诉提供者(依赖注入容器)所需服务的类型,后者就能根据预先注册的规则提供一个匹配的服务实例。由于服务注册最终决定了依赖注入容器根据指定的服务类型会提供一个怎样的服务实例,所以我们可以通过修改服务注册的方式来实现对框架的定制。如果应用程序需要采用前面定义的SingletonControllerActivator以单例的模式来激活目标Controller,那么它可以在启动MvcEngine之前按照如下的形式将SingletonControllerActivator注册到依赖注入容器上就可以了。

public class App
{
static void Main(string[] args)
{
var cat = new Cat() .Register<ControllerActivator, SingletonControllerActivator>();
var engine = new MvcEngine(cat);
var address = new Uri("http://localhost/mvcapp");
engine.StartAsync(address);
}
}

二、三种依赖注入方式

一项任务往往需要多个对象相互协作才能完成,或者说某个对象在完成某项任务的时候需要直接或者间接地依赖其他的对象来完成某些必要的步骤,所以运行时对象之间的依赖关系是由目标任务来决定的,是“恒定不变的”,自然也无所谓“解耦”的说法。但是运行时对象通过对应的类来定义,类与类之间耦合则可以通过对依赖进行抽象的方式来降低或者解除。

从服务消费的角度来讲,我们借助于一个接口对消费的服务进行抽象,那么服务消费程序针对具体服务类型的依赖可以转移到对服务接口的依赖上面,但是在运行时提供给消费者的总是一个针对某个具体服务类型的对象。不仅如此,要完成定义在服务接口的操作,这个对象可能需要其他相关对象的参与,换句话说,提供的这个依赖服务对象可能具有对其他服务对象的依赖。作为服务对象提供者的依赖注入容器,它会根据这一依赖链提供所有的依赖服务实例。

如下图所示,应用框架调用GetService<IFoo>方法向依赖注入容器索取一个实现了IFoo接口的服务对象,后者会根据预先注册的类型映射关系创建一个类型为Foo的对象。由于Foo对象需要Bar和Gux对象的参与才能完成目标操作,所以Foo具有了针对Bar和Gux的直接依赖。至于服务对象Bar,它又依赖Baz,那么Baz成为了Foo的间接依赖。对于依赖注入容器最终提供的Foo对象,它所直接或者间接依赖的对象Bar、Baz和Qux都会预先被初始化并自动注入到该对象之中。

640?wx_fmt=png

从面向对象编程的角度来讲,类型中的字段或者属性是依赖的一种主要体现形式。如果类型A中具有一个B类型的字段或者属性,那么A就对B产生了依赖,所以我们可以将依赖注入简单地理解为一种针对依赖字段或者属性的自动化初始化方式。我们可以通过三种主要的方式达到这个目的,这就是接下来着重介绍的三种依赖注入方式。

构造器注入

构造器注入就是在构造函数中借助参数将依赖的对象注入到由它创建的对象之中。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性Bar上,针对该属性的初始化实现在构造函数中,具体的属性值由构造函数传入的参数提供。

public class Foo
{
public IBar Bar{get;}
public Foo(IBar bar) =>Bar = bar;
}

除此之外,构造器注入还体现在对构造函数的选择上。如下面的代码片段所示,Foo类定义了两个构造函数,依赖注入容器在创建Foo对象之前首先需要选择一个适合的构造函数。至于目标构造函数如何选择,不同的依赖注入容器可能有不同的策略,比如可以选择参数最多或者最少的构造函数,或者可以按照如下所示的方式在目标构造函数上标注一个InjectionAttribute特性。

public class Foo
{
public IBar Bar{get;}
public IBaz Baz {get;}

[Injection]
public Foo(IBar bar) =>Bar = bar;
public Foo(IBar bar, IBaz):this(bar)=>Baz = baz;
}

属性注入

如果依赖直接体现为类的某个属性,并且该属性不是只读的,我们可以让依赖注入容器在对象创建之后自动对其进行赋值进而达到依赖注入的目的。一般来说,我们在定义这种类型的时候,需要显式将这样的属性标识为需要自动注入的依赖属性以区别于其他普通的属性。如下面的代码片段所示,Foo类中定义了两个可读写的公共属性Bar和Baz,我们通过标注InjectionAttribute特性的方式将属性Baz设置为自动注入的依赖属性。对于由依赖注入容器提供的Foo对象,它的Baz属性将会自动被初始化。

public class Foo
{
public IBar Bar{get; set;}

[Injection]
public IBaz Baz {get; set;}
}

方法注入

体现依赖关系的字段或者属性可以通过方法的形式初始化。如下面的代码片段所示,Foo针对Bar的依赖体现在只读属性上,针对该属性的初始化实现在Initialize方法中,具体的属性值由该方法的传入的参数提供。我们同样通过标注特性(InjectionAttribute)的方式将该方法标识为注入方法。依赖注入容器在调用构造函数创建一个Foo对象之后,它会自动调用这个Initialize方法对只读属性Bar进行赋值。

public class Foo
{
public IBar Bar{get;}

[Injection]
public Initialize(IBar bar)=> Bar = bar;
}

除了上述这种通过依赖注入容器在初始化服务过程中自动调用的实现之外,我们还可以利用它实现另一种更加自由的方法注入,这种注入方式在ASP.NET Core应用中具有广泛的应用。ASP.NET Core在启动的时候会调用注册的Startup对象来完成中间件的注册,我们定义这个Startup类型的时候不需要让它实现某个接口,所以用于注册中间件的Configure方法没有一个固定的声明,我们可以按照如下的方式将任意依赖的服务实例直接注入到这个方法中。

public class Startup
{
public void Configure(IApplicationBuilder app, IFoo foo, IBar bar, IBaz baz);
}

类似的注入方式同样应用到中间件类型的定义上。与用来注册中间件的Startup类型一样,ASP.NET Core框架下的中间件类型同样不需要实现某个预定义的接口,用于处理请求的InvokeAsync或者Invoke方法同样可以按照如下的方式注入任意的依赖服务。

public class FoobarMiddleware
{
private readonly RequestDelegate _next;
public FoobarMiddleware(RequestDelegate next)=> _next = next;

public Task InvokeAsync(HttpContext httpContext, IFoo foo, IBar bar, IBaz baz);
}

上面这种方式的方法注入促成了一种“面向约定”的编程方式。由于不再需要实现某个预定义的接口或者继承某一个预定义的基类,需要实现或者重写方法的声明也就少了对应的限制,这样就可以采用最直接的方式将依赖的服务注入到方法中。对于前面介绍的这几种注入方式,构造器注入是最为理想的形式,我个人不建议使用属性注入和方法注入(前面介绍的这种基于约定的方法注入除外)。

三、Service Locator模式

假设我们需要定义一个服务类型Foo,它依赖于另外两个服务Bar和Baz,后者对应的服务接口分别为IBar和IBaz。如果当前应用中具有一个依赖注入容器(假设类似于我们在前面定义的Cat),那么我们可以采用如下两种方式来定义这个服务类型Foo。

public class Foo : IFoo
{
public IBar Bar { get; }
public IBaz Baz { get; }
public Foo(IBar bar, IBaz baz)
{
Bar = bar;
Baz = baz;
}
public async Task InvokeAsync()
{
await Bar.InvokeAsync();
await Baz.InvokeAsync();
}
}

public class Foo : IFoo
{
public Cat Cat { get; }
public Foo(Cat cat) => Cat = cat;
public async Task InvokeAsync()
{
await Cat.GetService<IBar>().InvokeAsync();
await Cat.GetService<IBaz>().InvokeAsync();
}
}

从表面上看,上面提供的这两种服务类型的定义方式貌似都不错,至少它们都解决针对依赖服务的耦合问题,并将针对服务实现的依赖转变成针对接口的依赖。那么哪一种更好呢?我想有人会选择第二种定义方式,因为这种定义方式不仅仅代码量更少,针对服务的提供也更加直接。我们直接在构造函数中“注入”了代表“依赖注入容器”的Cat对象,在任何使用到依赖服务的地方,我们只需要利用它来提供对应的服务实例就可以了。

但事实上第二种定义方式采用的设计模式根本就不是“依赖注入”,而是一种被称为“Service Locator”的设计模式。Service Locator模式同样具有一个通过服务注册创建的全局的容器来提供所需的服务实例,该容器被称为“Service Locator”。“依赖注入容器”和“Service Locator”实际上是同一事物在不同设计模式中的不同称谓罢了,那么依赖注入和Service Locator之间的差异体现在什么地方呢?

我觉得可以从“依赖注入容器”或者“Service Locator”被谁使用的角度来区分这两种设计模式的差别。在一个采用依赖注入的应用中,我们只需要采用标准的注入形式将服务类型定义好,并在应用启动之前完成相应的服务注册就可以了,框架自身的引擎在运行过程中会利用依赖注入容器来提供当前所需的服务实例。换句话说,依赖注入容器的使用者应该是框架而不是应用程序。Service Locator模式显然不是这样,很明显是应用程序在利用它来提供所需的服务实例,所以它的使用者是应用程序

我们也可以从另外一个角度区分两者之间的差别。由于依赖服务是以“注入”的方式来提供的,所以采用依赖注入模式的应用可以看成是将服务“推”给依赖注入容器,Service Locator模式下的应用则是利用Service Locator去“”取所需的服务,这一推一拉也准确地体现了两者之间的差异。那么既然两者之间有差别,究竟孰优孰劣呢?

早在2010年,Mark Seemann就在他的博客中将Service Locator视为一种“反模式(Anti-Pattern)”,虽然也有人对此提出不同的意见,但我个人是非常不推荐使用这种设计模式的。我反对使用Service Locator与前面提到的反对使用属性注入和方法注入具有类似的缘由。

本着“松耦合、高内聚”的设计原则,我们既然将一组相关的操作定义在一个能够复用的服务中,就应该尽量要求服务自身不但具有独立和自治的特性,也要求服务之间的应该具有明确的界限,服务之间的依赖关系应该是明确的而不是模糊的。不论是采用属性注入或者方法注入,还是使用Service Locator来提供当前依赖的服务,这无疑为当前的服务增添了一个新的依赖,即针对依赖注入容器或者Service Locator的依赖。

当前服务针对另一个服务的依赖与针对依赖注入容器或者Service Locator的依赖具有本质的不同,前者是一种基于类型的依赖,不论是基于服务的接口还是实现类型,这是一种基于“契约”的依赖。这种依赖不仅是明确的,也是有保障的。但是依赖注入容器或者Service Locator本质上是一个黑盒,它能够提供所需服务的前提是相应的服务注册已经预先添加了容器之中,但是这种依赖不仅是模糊的也是不可靠的。

ASP.NET Core框架使用的依赖注入框架只支持构造器注入,而不支持属性和方法注入(类似于Startup和中间件基于约定的方法注入除外),但是我们很有可能不知不觉地会按照Service Locator模式来编写我们的代码。从某种意义上讲,当我们在程序中使用IServiceProvider(表示依赖注入容器)来提取某个服务实例的时候,就意味着我们已经在使用Service Locator模式了,所以当我们遇到这种情况下的时候应该多想一想是否一定需要这么做。

原文链接:https://www.cnblogs.com/artech/p/inside-asp-net-core-03-03.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313331.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软100题第5题

转载自&#xff1a;http://blog.csdn.net/littlestream9527/article/details/8104731 http://blog.csdn.net/v_july_v/article/details/6370650 http://blog.csdn.net/insistgogo/article/details/7689297 下面&#xff0c;我试图用最清晰易懂&#xff0c;最易令人理解的思维…

北京Dotnet分享会 || 精英论坛第三期

编者按&#xff1a;没有一成不变的定律&#xff0c;没有长久不衰的流行&#xff0c;更没有一劳永逸的侥幸&#xff0c;只有自己刻苦努力、脚踏实地、兢兢业业的学习和工作&#xff0c;才会成为这个社会永远不会被淘汰的中流砥柱。一、昨夜西风凋碧树昨夜西风凋碧树&#xff0c;…

微软开源微服务运行时Dapr,赋能云原生应用开发

Dapr 是一个可移植的、由事件驱动的 Serverless 运行时&#xff0c;用于跨云和边缘构建分布式应用程序。10月9日&#xff0c;正式以 MIT 协议开源。Dapr 使开发人员能够轻松地构建弹性、无状态和有状态的微服务&#xff0c;让它们在云和边缘位置上运行&#xff0c;并包含了开发…

最大堆和最小堆

堆和栈的区别&#xff1a;一、堆栈空间分配区别&#xff1a;1、栈&#xff08;操作系统&#xff09;&#xff1a;由操作系统自动分配释放 &#xff0c;存放函数的参数值&#xff0c;局部变量的值等。其操作方式类似于数据结构中的栈&#xff1b;2、堆&#xff08;操作系统&…

认知的高度 = 人生的高度

大家好&#xff0c;我是Z哥。我们每个人&#xff0c;每天要和很多不同的人打交道。我相信下面的场景每个程序员都有遇到过&#xff0c;业务方在某个模块下新提出了一个功能&#xff0c;但是你希望做新功能之前先把这个模块的低质量代码重构一下。但是&#xff0c;不管你怎么摆事…

VSCode开发.NETCore项目入门(1)设置中文语言环境

安装VSCode最新地址&#xff1a;https://code.visualstudio.com/&#xff0c;下载后安装即可配置语言环境打开安装好的VSCode软件&#xff0c;可以看到刚刚安装的VSCode软件默认使用的是英文语言环境&#xff0c;如下图&#xff1a;使用快捷键【CtrlShiftP】来配置&#xff0c;…

微软100题第11题

参照&#xff1a;http://blog.csdn.net/caryaliu/article/details/8107089 参照&#xff1a;http://blog.csdn.net/lalor/article/details/7626678 把二叉树看成一个图&#xff0c;父子节点之间的连线看成是双向的&#xff0c;我们姑且定义"距离"为两个节点之间的个…

.NET Core 3.0】框架之十三 || 部署攻略

本文有配套视频&#xff1a;https://www.bilibili.com/video/av58096866/?p9一、部署1、WIN_独立部署感谢群里&#xff08;白云&#xff09;小伙伴&#xff0c;博主 小淋科技 提出的方案(需要 netcore2.1 )&#xff0c;我竟然忽略了&#xff0c;该打该打&#xff0c;官档都读…

Python import以及os模块

转自&#xff1a;http://jianpx.iteye.com/blog/486466 http://blog.chinaunix.net/uid-27838438-id-4087978.html Import: 1. import 实际上是python虚拟机把当前的globals()和locals()传进__builtins__.__import__内置函数了&#xff0c;所以实际上干活的是那个__import__函…

.Net Core3.0 配置Configuration

准备.NET core和.NET项目配置上有了很大的改变&#xff0c;支持的也更加丰富了比如命令行&#xff0c;环境变量&#xff0c;内存中.NET对象&#xff0c;设置文件等等。.NET项目我们常常把配置信息放到webConfig 或者appConfig中。配置相关的源码https://github.com/aspnet/Exte…

asp.net core 3.0 中使用 swagger

asp.net core 3.0 中使用 swaggerIntro上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用&#xff0c;那个项目的 api 比较简单&#xff0c;都是匿名接口不涉及到认证以及 api 版本控制&#xff0c;最近把另外一个 api 项目升级到了 3.0&#xff0c;还是遇到了一些…

由微软100题“求和不用for while”引出的static类成员的知识

转自&#xff1a;http://www.cnblogs.com/gysm/archive/2011/09/16/2179277.html C类中谈到static,我们可以在类中定义static成员&#xff0c;static成员函数&#xff01;Cprimer里面讲过&#xff1a;static成员它不像普通的数据成员&#xff0c;static数据成员独立于该类的任意…

MCN是啥?了解一下这5个互联网热词

骗子刷量&#xff0c;黑吃黑半斤八两前几天一件事火爆了互联网圈&#xff0c;一场搞笑的骗局&#xff0c;一场蜂群传媒导演的“僵尸舞台剧”&#xff1a;一条一夜爆红的视频 流量却为0&#xff01;。一个电商商家卖产品有投放需求&#xff0c;找到了微博上一家 MCN 机构的一个女…

为什么我不建议你去外包公司?

前言在我离开上家公司之前&#xff0c;我的直属领导找我聊了一番。除了问候我有没有找好下家之外&#xff0c;还千叮咛万嘱咐我千万不要去外包公司&#xff0c;否则会在简历上留下无法磨灭的污点。当时的我对于外包公司的了解并不深&#xff0c;只是道听途说外包公司很坑&#…

友浩达优选上新,原生态农产品,买得安心,吃得放心

大闸蟹还在热卖&#xff0c;需要的同学可以访问 各位一直支持队长的朋友们友浩达优选上新了本着为大家推荐好东西的想法商城里上架的商品都是队长亲自挑选有质量保证的口碑好商品这次&#xff0c;来看看队长又给大家带了哪些好东西本次上新全是各地优选原生态农产品食品优质、安…

树莓派4上跑 .NET Core 3.0,这次,真·64位!

导语前不久我写了一篇《Gentoo由于 Windows 10 IoT Core &#xff08;以及上面的UWP们&#xff09;暴尸荒野而苟且偷生使用 Linux 的我&#xff0c;已经彻底开荤了。最近我发现有个叫 Gentoo 的 Linux 系统&#xff0c;支持树莓派4的64位CPU。项目地址&#xff1a;https://gith…

asp.net core 使用 AccessControlHelper 控制访问权限

asp.net core 使用 AccessControlHelper 控制访问权限Intro由于项目需要&#xff0c;需要在基于 asp.net mvc 的 Web 项目框架中做权限的控制&#xff0c;于是才有了这个权限控制组件&#xff0c;最初只是支持 netframework&#xff0c;后来 dotnetcore 2.0 发布了之后添加了对…

Caffe 增加自定义 Layer 及其 ProtoBuffer 参数

转载自&#xff1a;http://blog.csdn.net/kkk584520/article/details/52721838 http://blog.csdn.net/kkk584520 博客内容基于新书《深度学习&#xff1a;21 天实战 Caffe》&#xff0c;书中课后习题答案欢迎读者留言讨论。以下进入正文。 在使用 Caffe 过程中经常会有这样的…

.NET Core 3.0愈加成熟,微软将不再把.NET Framework API移植给它

目前 .NET Core 3.0 拥有的 API 总数约为 .NET Framework API 的 80%&#xff0c;剩下尚未从 .NET Framework 移植到 .NET Core 的 API&#xff0c;微软考虑以开源的形式发布。微软方面表示&#xff0c;通过 .NET Core 3.0&#xff0c;他们现在已具备轻松移植现代 workload 所需…

参加 JSConf China 2019 是怎样的体验?VS Code 和 TypeScript 都很火

JSConf China 2019 于 10 月 19-20 日于上海尚浦中心举行。很高兴作为讲师参加这次的 JSConf。Day 1在 Day 1 给大家聊了聊 The Beauty of TypeScript。简单总结下我讲的 TypeScript 的 session。千言万语&#xff0c;汇聚成下面两页的 PPT。TypeScript 的使用场景&#xff08;…