五、SOLID 原则,控制反转,依赖注入

在本章中,我们将讨论以下主题:

  • 在 ASP 中使用 Native IOC 实现 DI.NET Core
  • 使用 ASP 中可用的生命周期.NET Core
  • 用 Autofac 实现 DI
  • 使用 StructureMap 实现 DI
  • 使用 DryIoc 实现 DI

介绍

在本章中,我们将学习什么是 SOLID 原则,什么是 IOC、DI 和服务定位器模式,为什么我们应该使用这些模式,它们解决了什么问题,我们可以使用什么工具来解决它们,以及它们是如何在 ASP 中本地实现的.NET 的核心。

那些知道所有关于 IOC 和 DI 的概念的人可以直接阅读本章的第一章。 接下来,我们将在本介绍中向您展示其中的一些概念。

首先,在使用面向对象语言编程时,我们必须遵循这些常见的设计原则:

  • 保持简单,愚蠢:我们必须保持代码简单,不要过于复杂。 愚蠢并不意味着愚蠢。
  • Don't Repeat Yourself:每一个知识都必须在一个系统中有一个单一的、明确的、权威的表达。 换句话说,我们试图在系统中使用抽象,避免重复和不重复逻辑。
  • 我们告诉物体要做什么,而不是问他们应该做什么。 这种实践促进了松耦合和封装。
  • You Ain't Gonna Need It:我们应该只编写我们需要的功能,而不是我们认为未来可能需要的东西。
  • 关注点分离:我们应该将应用划分为不同的特性,这样我们就可以轻松地重用和维护这些特性或行为。

坚实的原则

即使您不是一个架构师,或者您不打算成为一个架构师,作为一个认真的开发人员,您在开发面向对象软件时也应该实现 SOLID 原则。 在这篇介绍中,我们将简要地介绍每一个原则,而不详细介绍它们。

我们需要的模式应该具有以下属性:

  • 松散耦合
  • 可测试性
  • 负责代表团
  • 灵活性

根据 Bob 叔叔(Robert Martin)的说法,SOLID 设计原则如下:

  • S -单一责任原则:每个对象都应该有单一的责任和单一的改变理由。
  • O -开闭原则:软件实体对责任是开放的,对修改是封闭的。
  • L - Liskov 替代原则:您应该能够替换一个接口实现,而不必破坏实际实现。 这种模式迫使我们使用接口,使我们的代码更易于测试。
  • I -接口隔离:不应该强迫客户端依赖于他们不使用的接口。
  • D -依赖注入:你应该编写一个接口或抽象类,而不是一个实现。

控制反转

术语控制反转指的是一组模式,当我们有很多回归 bug 或者很难重新部署代码时,这些模式可以帮助我们。

当我们增加应用的复杂性时,这些模式的不便就变得很明显了; 如果没有很好地使用和理解它们,它们可能会产生很大的性能问题,并且很难调试。

当使用反转控件模式时,我们希望反转类对其依赖项的控制。 问题是,当我们在其他类中使用类时,修改低级类将影响高级类。

这就是为什么我们更喜欢依赖于抽象而不是具体的实现。

IOC是遵循好莱坞原则的一种方式:不要叫我们; 我们会给您打电话

使用 IOC,代码的业务逻辑流依赖于在程序执行期间构建的对象图,而不是由静态地相互绑定的对象确定。

有几种实现 IOC 的设计模式:

  • 模板方法模式:一个或多个算法步骤可以被子类覆盖,以允许不同的行为,同时确保仍然遵循总体算法。
  • 策略模式:一个算法会独立于使用它的客户而变化,并且必须是可互换的。
  • 服务定位器模式:该模式包含对一组服务的引用,并封装定位这些服务的逻辑。 使用它来获取服务实例。
  • 工厂模式:该模式将一个类的实例化委托给另一个类。
  • 依赖注入模式:高级模块不应该依赖低级模块; 两者都应该依赖于抽象。

依赖注入(DI)

依赖注入是实现IOC的几种方法之一。

当类彼此之间的协作,更准确地说,当我们使用作文两个类之间(B 类是类的属性)——我们说我们有强耦合的对象,这是一个坏在面向对象的编程实践。 A类对B类过于依赖,对B类的修改可能会严重影响。**

为了避免这种强依赖性,我们可以使用类B的抽象。 我们可以使用一个名为IB的接口来替换类B作为一个属性,然后将接口 IB 的解析与这个具体的类委托给一个层组件,例如 DI 容器。

你不需要使用 DI 容器来做这件事,但是如果我们在构造函数中使用new来实例化对象,那就会被称为穷人的 DI:

public class ProductService
{ 
  private readonly IProductRepository productRepository; 
  public ProductService () : this(new ProductRepository())
  {}
  public ProductService (ProductRepository 
  productRepository)
  {    
    this.productRepository = productRepository;
  } 
}  

使用 DI 开发应用是一种最佳实践,但如果性能成本大于它带来的好处,我们应该考虑它,并使用性能测试对其进行测试。

现在让我们看看如何实现 DI。

我们有几种从接口注入依赖项的方法:

  • 通过构造函数(构造函数注入)
  • 按类属性(setter 注入)
  • 通过接口(对于可伸缩性来说很有趣,不过根据实例化的位置,我们可能需要不同的生命周期)

IOC 在使用构造函数注入时是有意义的。

对依赖关系的控制只在构造函数中反转。 这就是为什么构造函数注入是实现 DI 的最佳实践。

集装箱

DI 容器遵循RRR(Register-Resolve-Release)模式。

DI 容器是一个框架或库,用于组合应用的对象图,在应用入口点创建一个组合根并管理它们的生命周期。 这个组合根注册要解析的对象及其抽象。

有很多免费的 DI 容器,例如以下:

  • 团结
  • Ninject
  • 结构图
  • Autofac
  • 简易喷射器
  • DryIoc
  • And many others

当创建组合根时,我们配置 DI 容器。 我们可以通过使用代码来实现这一点,如果我们选择的 DI 容器具有这种功能,也可以通过 XML 配置文件来实现。

配置 DI 容器

让我们看一个使用 StructureMap DI 容器通过代码进行配置的例子:

public static class IoC 
{         
  public static IContainer Initialize() 
  { 
    ObjectFactory.Initialize(x => 
    { 
      x.For() 
      .Use(); 
    }); 
    return ObjectFactory.Container; 
  } 
}  

我们从global.asax中的IoC静态类中调用Initialize方法:

public class MvcApplication :     System.Web.HttpApplication
{         
  protected void Application_Start()
  { 
    IoC.Initialise(); 
    ...
  } 
} 

global.asaxapplication_start方法中组合根的一个例子,它是 MVC 5 应用的入口点。

我们可以看到,在第五行中,我们正在使用IDependencyResolver实例要求它用我们选择的 DIDependencyResolver容器替换当前的 MVCDependencyResolver

如果我们使用 ASP.NET Web API,我们应该要用另一个DependencyResolverSignalR也是如此; 这个库也有自己的机制来调用自己的类来调用它的DependencyResolver

The DependencyResolver is used until MVC 5 to resolve many different types in the ASP.NET MVC framework internally. It is used by the controller factory to get controller instances.

Using an IOC/DI container, we create and use a custom dependency resolver.

现在,让我们看一个 XML 配置示例。

XML 配置文件的优点是,我们可以更改应用的组合根,而无需重新编译。

这样做的缺点是,如果我们解析的抽象或具体类型不存在,我们在编译时不会出现错误(我们可能在写名称时犯了错误)。

一些 DI 容器使用面向 xml 的配置,而另一些使用更面向代码的配置。

IOC 与服务定位器

服务定位器是用于定位其他服务的设计模式。 它是 DI 的反义词。 根据其使用情况,当我们将依赖关系移动到服务定位器而不是具体类时,它可以是一个反模式。

使用服务定位器的另一个问题是,通常很难更改实现和测试系统。 采用相反的方法,当应用第一次启动时,IOC 为每个应用构建一次依赖关系。

DI 的目标是在应用初始化期间注入依赖项。 如果我们稍后调用依赖项,这将成为一个服务位置。

生命周期

根据我们选择的 DI 容器,我们将有更多或更少的可用生命周期。

更常见的生命周期如下:

  • Transient:对象实例在其所在的代码块中存在时,它将存在于内存中

  • 单例:对象实例在显式释放之前一直存在于内存中

  • 每个请求:对象实例在实例化 HTTP 请求时存在于内存中

根据我们选择的 DI 容器,还有很多其他可用的生命周期。

在 ASP 中使用 Native IOC 实现 DI.NET Core

在本教程中,我们将以一种简单的方式学习如何注册、解析和为 ASP 中的抽象提供一个生命周期.NET 的核心。

准备

依赖注入现在在 ASP 中是原生的.NET 的核心。 Microsoft.Extensions.DependencyInjection【进口 T7】在project.json``web.config(以前),并通过使用声明Startup.cs``global.asax(以前),我们可以使用这个内部组件为我们的应用管理 DI,由IServiceProvider界面。

怎么做……

我们将通过在 ASP 的HomeController中使用构造函数注入ServiceProducts类.NET MVC 6 应用:

  1. 首先,我们创建一个名为Product的类:

  1. 然后创建一个名为IProductService:的接口

  1. 接下来,我们创建一个名为ProductService的类:

  1. 让我们在HomeController中使用ProductService,通过在HomeController构造函数中注入ProductService抽象来创建ProductService的实例。 ProductService将适用于整个HomeController班:

  1. 让我们添加视图:

  1. 最后,我们用ConfigureServices方法配置ProductService生命周期:

我们现在可以看到一个 HTML 页面的结果:

IServiceCollection接口已经包含了一些帮助器来管理框架中最常用对象的依赖关系。

我们可以使用这些帮助程序,并为我们想要解决的新库创建其他帮助程序。

应用中实例化的每个对象的生命周期可以通过IServiceCollection管理:

它是如何工作的…

ASPNET CORE 中可用的生命周期如下:

  • 实例:实例一直被给定。 我们负责它的创造。
  • Transient:每次使用该对象时创建该实例。
  • 作用域:HTTP 请求只创建一次实例。
  • 单例:应用实例只创建一次实例。

ASP。 网的核心,我们可以注入依赖项属性,但最佳实践是使用构造函数,创建根组成,我们必须登记,决心,并添加对象的生命周期的ConfigureServices``Startup.cs文件的方法。

在我们的示例中,我们为应用创建了依赖项的对象图。

为此,我们使用传递给ConfigureService方法参数的IServiceCollection对象。

我们创建了一个ServiceProduct类来在控制器中使用。 我们在ConfigureService方法中配置了该类的行为。

目前,有很多限制,比如解析类型时只有一个构造函数,只支持构造函数注入,生命周期的数量有限。

为了使用更多的生命周期,我们将不得不使用我们最喜欢的 DI 容器,如果它已经移植到ASP.NET Core

有更多的…

想要阅读一些非常有趣的内容,请尝试 Mark Seemann 关于依赖注入的书,在http://amzn.to/12p90MG

我们也可以阅读 Mark Seemann 的博客http://blog.ploeh.dk/

使用 ASP 中可用的生命周期.NET Core

在本菜谱中,我们将看到一个包含所有可用生命周期的示例,并查看它涉及的内容。

准备

我们将看到所有这些生命周期之间的区别。

怎么做……

  1. 首先,让我们创建一个名为IOperation的接口,该接口具有Guid类型的 getter,以及四个接口,分别代表每个可用生命周期的一个任务:

  1. 现在我们将在一个Operation类中实现这些接口:

  1. Startup.cs类的ConfigurationServices方法中,我们管理每个接口的生命周期:

  1. 现在,让我们创建一个将在 MVC 控制器中使用的OperationService类,并在几个 HTTP 请求中执行:

  1. 现在,我们创建 MVC 控制器并添加一个Index动作来显示每个实现的Guid值:

  1. 最后,我们创建了显示结果的视图:

它是如何工作的…

我们可以看到Guids的不同值。

要求 1:

要求 2:

结论如下:

  • Transient:对象总是不同的; 为每个控制器和每个服务提供一个新的实例
  • 作用域:对象在一个请求中是相同的,但是在不同的请求中是不同的
  • 单例:对象对于每个对象和每个请求都是相同的
  • 实例:对象对于每个对象和每个请求都是相同的,并且是代码中指定的确切实例

用 Autofac 实现 DI

在本菜谱中,我们将使用 Autofac 作为 DI 容器来组成应用的对象图。 它很有名,性能好,而且有很多特点。

ASP.NET Core 有一个开箱即用的 DI 容器。 在本章中,我们将使用另一个 DI 容器 autofac。 Autofac 在社区中非常受欢迎。

内置的 DI 容器是非常轻量级的,并不支持成熟的 DI 容器所支持的所有特性。

Autofac 也很轻,但它几乎拥有一个完整的 DI 容器库所需要的所有特性。

准备

在前面的两个配方中,我们通过使用 ASP 中的本地 IOC 组件解决了对ServiceProduct的依赖.NET 的核心。 这一次,这将由名为Autofac的第三方组件完成。 现在,在 ASP.NET 应用,我们将能够使用相同的 IOC 容器管理 MVC、WebAPI 和 SignalR 组件。

怎么做……

和第一个食谱一样,我们做以下操作:

  1. 通过在 ASP 的HomeController中使用构造函数注入ServiceProducts类.NET MVC 6 应用。
  2. 创建一个名为Product的类。
  3. 创建一个名为IProductService.的接口
  4. 创建一个名为ProductService的类。
  5. 修改HomeController以添加一个将注入IProductService.的构造函数
  6. HomeController中创建一个名为Products的操作方法。
  7. 创建一个名为Products.cshtml的视图。

  8. 现在,要使用 Autofac,我们必须在project.json文件的依赖部分中添加AutofacAutofac.Extensions.DependencyInjectionNuGet 包:

  1. 我们使用一个模块来帮助我们配置依赖项:

  1. 我们在Startup.cs中的ConfigureService方法中配置 Autofac DI 组件:

现在,当返回IServiceProvider时,我们不再显式地使用DependencyResolver; ASP.NET Core 在内部为我们做这些。

ASP.NET Core 在 IServiceProvider 中为我们使用了DependencyResolver的内置功能。 有了这个支持,我们就不应该显式地使用DependencyResolver

  1. 最后,我们使用新的配置测试应用:

它是如何工作的…

让我们在HomeController中使用ProductService,通过在HomeController构造函数中注入ProductService抽象来创建ProductService的实例。

ProductService将适用于整个HomeController班。

我们将使用一个模块来帮助我们配置依赖项。

根据 Autofac 文档:

A module is a small class that can be used to bundle up a set of related components behind a 'facade' to simplify configuration and deployment. The module exposes a deliberate, restricted set of configuration parameters that can vary independently of the components used to implement the module.

使用 StructureMap 实现 DI

现在,我们将使用一个名为 StructureMap 的 DI 容器作为外部组件来解决依赖关系。

在这个配方中,我们将使用另一个 DI 容器 StructureMap。 StructureMap 在社区中非常受欢迎。

StructureMap 也很轻,但它几乎拥有一个完整的 DI 容器库所需要的所有特性。

怎么做……

我们将为应用创建依赖项的对象图。 为此,我们将创建一个返回IServiceProvider类型的ConfigureService方法。

和第一个食谱一样,我们将做以下工作:

  1. 通过在 ASP 的HomeController中使用构造函数注入ServiceProducts类.NET MVC 6 应用。
  2. 创建一个名为Product的类。
  3. 创建一个名为IProductService.的接口
  4. 创建一个名为ProductService的类。
  5. 修改HomeController以添加一个将注入IProductService.的构造函数
  6. HomeController中创建一个名为 Products 的操作方法。
  7. 创建一个名为Products.cshtml的视图。
  8. 现在,要使用 StructureMap,我们必须在project.json文件的dependencies部分中添加 StructureMap 和structuremap.DnxNuGet 包。 它们会自动下载:

  1. ConfigureServices方法中,我们创建了一个 StructureMap 容器:

  1. 最后,我们使用新的配置测试应用:

它是如何工作的…

我们使用传入的注册类型的形参ServiceCollection来填充容器。

调用 StructureMap 容器的 populate 方法在内部调用Configure方法。

使用 DryIoc 实现 DI

现在,我们将使用名为 DryIoc 的 DI 容器作为外部组件来解决依赖关系。

在这个配方中,我们将使用另一个 DI 容器- dryioc。 DryIoc 非常受社区欢迎。

DryIoc 也很轻,但它几乎拥有一个完整的 DI 容器库所需要的所有特性。

准备

我们想要组合应用的对象图。

为此,我们将创建一个返回IServiceProvider类型的ConfigureService方法。

怎么做……

和第一个食谱一样,我们将做以下工作:

  1. 通过在 ASP 的HomeController中使用构造函数注入ServiceProducts类.NET MVC 6 应用。
  2. 创建一个名为Product的类。
  3. 创建一个名为IProductService.的接口
  4. 创建一个名为ProductService的类。
  5. 修改HomeController以添加一个将注入IProductService.的构造函数

  6. HomeController中创建一个名为Products的操作方法。

  7. 创建一个名为Products.cshtml的视图。
  8. 现在,要使用 DryIoc,我们将在project.json文件的dependencies部分添加DryIocDryIoc.Dnx.DependencyInjectionNuGet 包。 它们会自动下载:

  1. 现在我们将创建一个引导类来注册并使用ProductService解析IProductService:

  1. 这个类也会在返回之前为我们解析ServiceProvider:

  1. ConfigureServices方法中,我们调用调用Bootstrap类的extension方法:

  1. 现在,让我们用这个新配置来测试应用:

它是如何工作的…

我们在一个名为IocDiExtensions的静态类中创建了一个名为ConfigureDI的扩展方法,为IServiceProvider创建一个包装器。**