自助建站设计工作主要包括,做任务的阅币漫画网站,为什么有的公司做很多个网站,黑龙江省网站备案IoC主要体现了这样一种设计思想#xff1a;通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用#xff0c;并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC#xff0c;比如我们在前面介绍的模板方法… IoC主要体现了这样一种设计思想通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC比如我们在前面介绍的模板方法、工厂方法和抽象工厂接下来我们介绍一种更有价值的IoC模式依赖注入DIDependency Injection。一、由容器提供对象和前面介绍的工厂方法和抽象工厂模式一样依赖注入是一种“对象提供型”的设计模式在这里我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在一个采用依赖注入的应用中我们定义某个类型的时候只需要直接将它依赖的服务采用相应的方式注入进来就可以了。在应用启动的时候我们会对所需的服务进行全局注册。一般来说服务大都是针对实现的接口或者继承的抽象类进行注册的服务注册信息的帮助我们在后续消费过程中提供对应的服务实例。按照“好莱坞法则”应用只需要定义并注册好所需的服务服务实例的提供则完全交给框架来完成框架则会利用一个独立的“容器Container”来提供所需的每一个服务实例。我们将这个被框架用来提供服务的容器称为“依赖注入容器”也有很多人将其称为“IoC容器”根据前面针对IoC的介绍我不认为后者是一个合理的称谓。依赖注入容器之所以能够按照我们希望的方式来提供所需的服务是因为该容器是根据服务注册信息来创建的服务注册了包含提供所需服务实例的所有信息。举个简单的例子我们创建一个名为Cat的依赖注入容器类型那么我们可以调用如下这个扩展方法GetServiceT从某个Cat对象中获取指定类型的服务对象。我之所以将其命名为Cat源于我们大家都非常熟悉的一个卡通形象“机器猫哆啦A梦”。机器猫的那个四次元口袋就是一个理想的依赖注入容器大熊只需要告诉哆啦A梦相应的需求它就能从这个口袋中得到相应的法宝。依赖注入容器亦是如此服务消费者只需要告诉容器所需服务的类型一般是一个服务接口或者抽象服务类就能得到与之匹配的服务实例。public static class CatExtensions{public static T GetServiceT(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.GetServiceIWebListener();var activator Cat.GetServiceIControllerActivator();var executor Cat.GetServiceIControllerExecutor();var renderer Cat.GetServiceIViewRenderer();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() .RegisterControllerActivator, SingletonControllerActivator();var engine new MvcEngine(cat);var address new Uri(http://localhost/mvcapp); engine.StartAsync(address); }}二、三种依赖注入方式一项任务往往需要多个对象相互协作才能完成或者说某个对象在完成某项任务的时候需要直接或者间接地依赖其他的对象来完成某些必要的步骤所以运行时对象之间的依赖关系是由目标任务来决定的是“恒定不变的”自然也无所谓“解耦”的说法。但是运行时对象通过对应的类来定义类与类之间耦合则可以通过对依赖进行抽象的方式来降低或者解除。从服务消费的角度来讲我们借助于一个接口对消费的服务进行抽象那么服务消费程序针对具体服务类型的依赖可以转移到对服务接口的依赖上面但是在运行时提供给消费者的总是一个针对某个具体服务类型的对象。不仅如此要完成定义在服务接口的操作这个对象可能需要其他相关对象的参与换句话说提供的这个依赖服务对象可能具有对其他服务对象的依赖。作为服务对象提供者的依赖注入容器它会根据这一依赖链提供所有的依赖服务实例。如下图所示应用框架调用GetServiceIFoo方法向依赖注入容器索取一个实现了IFoo接口的服务对象后者会根据预先注册的类型映射关系创建一个类型为Foo的对象。由于Foo对象需要Bar和Gux对象的参与才能完成目标操作所以Foo具有了针对Bar和Gux的直接依赖。至于服务对象Bar它又依赖Baz那么Baz成为了Foo的间接依赖。对于依赖注入容器最终提供的Foo对象它所直接或者间接依赖的对象Bar、Baz和Qux都会预先被初始化并自动注入到该对象之中。从面向对象编程的角度来讲类型中的字段或者属性是依赖的一种主要体现形式。如果类型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.GetServiceIBar().InvokeAsync();await Cat.GetServiceIBaz().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