通常,在实例化对象时调用类构造函数,并将对象需要的任何值作为参数传递给构造函数。 这是依赖注入的一个例子,具体称为构造函数注入。 对象需要的依赖项被注入到构造函数中。
通过将依赖项指定为接口类型,依赖注入可以使具体类型与依赖于这些类型的代码进行解耦。 它通常使用一个容器来存储接口和抽象类型之间的注册和映射列表,以及实现或扩展这些类型的具体类型。
还有其他类型的依赖注入,如属性设置器注入和方法调用注入,但它们不太常见。 因此,本章将仅关注使用依赖注入容器执行构造函数注入。
依赖注入简介
依赖注入是反转控制(IoC)模式的专门版本,其中被反转的关注点是获得所需依赖关系的过程。 使用依赖注入,另一个类负责在运行时将依赖关系注入对象。 以下代码示例显示了在使用依赖注入时ProfileViewModel类的结构:
点击(此处)折叠或打开
-
public class ProfileViewModel : ViewModelBase
-
{
-
private IOrderService _orderService;
-
-
public ProfileViewModel(IOrderService orderService)
-
{
-
_orderService = orderService;
-
}
-
...
- }
ProfileViewModel构造函数接收一个IOrderService实例作为参数,由另一个类注入。 ProfileViewModel类中唯一的依赖关系在接口类型上。 因此,ProfileViewModel类对于负责实例化IOrderService对象的类没有任何知识。 负责实例化IOrderService对象并将其插入到ProfileViewModel类中的类称为依赖注入容器。
依赖注入容器通过提供实例化类实例的功能并根据容器的配置来管理其使用寿命来减少对象之间的耦合。 在创建对象期间,容器将注入对象所需的任何依赖项。 如果这些依赖关系尚未创建,容器首先创建并解析它们的依赖关系。
注意:依赖注入也可以使用工厂手动实施。 但是,使用容器可以通过组装扫描提供诸如生命周期管理和注册等附加功能。
使用依赖注入容器有几个优点:
- 容器不需要一个类定位其依赖关系并管理其生命周期。
- 容器允许映射实现的依赖关系而不影响类。
- 通过允许依赖关系被嘲笑,容器便于测试。
- 通过允许将新类轻松添加到应用程序中,容器增加了可维护性。
在使用MVVM的Xamarin.Forms应用程序的上下文中,依赖注入容器通常用于注册和解析视图模型,以及注册服务并将其注入到视图模型中。
有许多依赖注入容器可用,eShopOnContainers移动应用程序使用Autofac来管理应用程序中的视图模型和服务类的实例化。 Autofac有助于构建松散耦合的应用程序,并提供依赖注入容器中常见的所有功能,包括注册类型映射和对象实例的方法,解析对象,管理对象生命周期,以及将依赖对象注入到其解析的对象的构造器中。 有关Autofac的更多信息,请参阅readthedocs.io上的。
在Autofac中,IContainer接口提供依赖注入容器。 图3-1显示了使用此容器时的依赖关系,它实例化IOrderService对象并将其注入到ProfileViewModel类中。
图3-1:使用依赖注入时的依赖关系
在运行时,容器必须知道它应该实例化的IOrderService接口的哪个实现,然后才能实例化一个ProfileViewModel对象。 这涉及到:
- 容器决定如何实例化实现IOrderService接口的对象。 这被称为注册。
- 实例化实现IOrderService接口的对象的容器和ProfileViewModel对象。 这被称为决议。
最终,应用程序将完成使用ProfileViewModel对象,它将可用于垃圾回收。 此时,如果其他类不共享相同的实例,垃圾收集器应该处理IOrderService实例。
???? 提示:写入容器不可知代码。 始终尝试编写与容器无关的代码,以将应用与正在使用的特定依赖关系容器分离。
注册
在将依赖关系注入到对象之前,必须首先向容器注册依赖关系的类型。 注册类型通常涉及将容器传递给实现接口的接口和具体类型。
通过代码在容器中注册类型和对象有两种方法:
- 使用容器注册类型或映射。 当需要时,容器将构建一个指定类型的实例。
- 将容器中的现有对象注册为单例。 当需要时,容器将返回对现有对象的引用。
???? 提示:依赖注射容器并不总是适合的。 依赖注入引入了对小应用程序可能不合适或有用的额外的复杂性和要求。 如果类没有任何依赖关系,或者不是其他类型的依赖关系,则将它放在容器中可能是没有意义的。 另外,如果一个类具有一组依赖关系,并且永远不会改变,那么将它放在容器中可能是没有意义的。
需要依赖注入的类型的注册应该通过应用程序中的单一方法执行,并且应该在应用程序的生命周期早期调用此方法,以确保应用程序知道其类之间的依赖关系。 在eShopOnContainers手机应用程序中,这是由ViewModelLocator类执行的,该类构建了IContainer对象,并且是应用程序中唯一持有该对象的引用的类。 以下代码示例显示了eShopOnContainers移动应用程序如何在ViewModelLocator类中声明IContainer对象:
点击(此处)折叠或打开
- private static IContainer _container;
类型和实例在ViewModelLocator类中的RegisterDependencies方法中注册。 这是通过首先创建一个ContainerBuilder实例来实现的,该实例在以下代码示例中被演示:
点击(此处)折叠或打开
- var builder = new ContainerBuilder();
然后将类型和实例注册到ContainerBuilder对象,并且以下代码示例演示了最常见的类型注册形式:
点击(此处)折叠或打开
- var builder = new ContainerBuilder();
此处显示的RegisterType方法将接口类型映射到具体类型。 它通知容器来实例化一个RequestProvider对象,当它实例化一个需要通过构造函数注入IRequestProvider的对象。
也可以直接注册具体类型,而不需要从接口类型映射,如下面的代码示例所示:
点击(此处)折叠或打开
- builder.RegisterType();
ProfileViewModel类型解析后,容器将注入其所需的依赖关系。
Autofac还允许实例注册,其中容器负责维护对类型的单例实例的引用。 例如,以下代码示例显示了当ProfileViewModel实例需要IOrderService实例时,eShopOnContainers移动应用程序如何注册具体类型:
点击(此处)折叠或打开
- builder.RegisterType().As().SingleInstance();
此处显示的RegisterType方法将接口类型映射到具体类型。 SingleInstance方法配置注册,以便每个从属对象接收相同的共享实例。 因此,只有一个OrderService实例将存在于容器中,该对象由需要通过构造函数注入IOrderService的对象共享。
实例注册也可以使用RegisterInstance方法执行,这在下面的代码示例中被证明:
点击(此处)折叠或打开
- builder.RegisterInstance(new OrderMockService()).As();
这里显示的RegisterInstance方法创建一个新的OrderMockService实例并将其注册到容器。 因此,容器中只存在一个OrderMockService实例,该实例由需要通过构造函数注入IOrderService的对象共享。
按照类型和实例注册,必须构建IContainer对象,这在以下代码示例中进行了说明:
点击(此处)折叠或打开
- _container = builder.Build();
在ContainerBuilder实例上调用Build方法构建一个新的依赖关系注入容器,其中包含已经创建的注册。
????提示:将IContainer视为不可变的。 虽然Autofac提供了更新方法来更新现有容器中的注册,但应尽可能避免调用此方法。 建造容器后,修改容器是有风险的,特别是在容器已被使用的情况下。有关详细信息,请参阅在readthedocs.io上。
解析度
注册一个类型后,可以将其解析或注入为依赖关系。 当一个类型被解析并且容器需要创建一个新的实例时,它会将任何依赖项注入实例。
一般来说,当一个类型被解决时,三件事之一发生:
1. 如果类型尚未注册,容器将引发异常。
2. 如果类型已经注册为单例,容器返回单例实例。 如果这是第一次调用该类型,容器将在需要时创建它,并保留对它的引用。
3. 如果类型未注册为单例,容器返回一个新实例,并且不保留对它的引用。
以下代码示例显示如何解决先前已注册Autofac的RequestProvider类型:
点击(此处)折叠或打开
- var requestProvider = _container.Resolve();
在此示例中,要求Autofac解析IRequestProvider类型的具体类型以及任何依赖关系。 通常,当需要特定类型的实例时调用Resolve方法。 有关控制已解析对象的生命周期的信息,请参阅。
以下代码示例显示了eShopOnContainers移动应用程序如何实例化查看模型类型及其依赖关系:
点击(此处)折叠或打开
- var viewModel = _container.Resolve(viewModelType);
在此示例中,要求Autofac解析所请求视图模型的视图模型类型,容器还将解析任何依赖关系。 解析ProfileViewModel类型时,解析的依赖关系是一个IOrderService对象。 因此,Autofac首先构造一个OrderService对象,然后将其传递给ProfileViewModel类的构造函数。 有关eShopOnContainers移动应用程序如何构建视图模型并将其与视图相关联的更多信息,请参阅。
注意:使用容器注册和解析类型具有性能成本,因为容器使用反射来创建每种类型,特别是如果为应用程序中的每个页面导航重建依赖关系。 如果有很多或很深的依赖,创造的成本可以显着增加。
管理已解决对象的使用寿命
注册类型后,Autofac的默认行为是在每次解析类型时创建一个新的注册类型实例,或者依赖机制将实例注入其他类时。 在这种情况下,容器不会保留对已解析对象的引用。 但是,注册实例时,Autofac的默认行为是将对象的生命周期作为单例进行管理。 因此,当容器在范围内时,该实例保留在范围内,并且当容器超出范围并被垃圾回收时,或者当代码明确地处置该容器时,该实例将被保留。
Autofac实例范围可用于指定Autofac从注册类型创建的对象的单例行为。 Autofac实例作用域管理由容器实例化的对象生命周期。 RegisterType方法的默认实例范围是InstancePerDependency作用域。 但是,SingleInstance范围可以与RegisterType方法一起使用,以便容器在调用Resolve方法时创建或返回类型的单例实例。 以下代码示例显示了如何指示Autofac创建NavigationService类的单例实例:
点击(此处)折叠或打开
- builder.RegisterType().As().SingleInstance();
INavigationService接口第一次解析后,容器将创建一个新的NavigationService对象,并保留对它的引用。 在INavigationService接口的任何后续分辨率上,容器返回对先前创建的NavigationService对象的引用。
注意:当容器被处理时,SingleInstance scope将配置创建的对象。
Autofac包含其他实例范围。 有关更多信息,请参阅readthedocs.io上的。
概要
依赖注入可以使具体类型与依赖于这些类型的代码进行解耦。 它通常使用一个容器来存储接口和抽象类型之间的注册和映射列表,以及实现或扩展这些类型的具体类型。
Autofac有助于构建松散耦合的应用程序,并提供依赖注入容器中常见的所有功能,包括注册类型映射和对象实例的方法,解析对象,管理对象生命周期以及将依赖对象注入到其解析的对象的构造器中。