Внедрение зависимостейDependency Injection

Как правило, конструктор класса вызывается при создании экземпляра объекта, а все значения, необходимые объекту, передаются в конструктор в качестве аргументов.Typically, a class constructor is invoked when instantiating an object, and any values that the object needs are passed as arguments to the constructor. Это пример внедрения зависимостей, который, в частности, называется внедрением конструктора.This is an example of dependency injection, and specifically is known as constructor injection. Зависимости, необходимые объекту, вставляются в конструктор.The dependencies the object needs are injected into the constructor.

Указывая зависимости как типы интерфейсов, внедрение зависимостей позволяет разделять конкретные типы от кода, зависящего от этих типов.By specifying dependencies as interface types, dependency injection enables decoupling of the concrete types from the code that depends on these types. Обычно используется контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.It generally uses a container that holds a list of registrations and mappings between interfaces and abstract types, and the concrete types that implement or extend these types.

Существуют также другие типы внедрения зависимостей, такие как внедрение свойстваи Внедрение вызова метода, но они являются менее часто видимыми.There are also other types of dependency injection, such as property setter injection, and method call injection, but they are less commonly seen. Поэтому в этой главе основное внимание уделяется только выполнению внедрения конструктора с помощью контейнера внедрения зависимостей.Therefore, this chapter will focus solely on performing constructor injection with a dependency injection container.

Введение в внедрение зависимостейIntroduction to Dependency Injection

Внедрение зависимостей представляет собой специализированную версию шаблона инверсии управления (IoC), где отзываемая проблема — это процесс получения необходимой зависимости.Dependency injection is a specialized version of the Inversion of Control (IoC) pattern, where the concern being inverted is the process of obtaining the required dependency. При внедрении зависимостей другой класс отвечает за внедрение зависимостей в объект во время выполнения.With dependency injection, another class is responsible for injecting dependencies into an object at runtime. В следующем примере кода показано, как ProfileViewModel структурирован класс при использовании внедрения зависимостей.The following code example shows how the ProfileViewModel class is structured when using dependency injection:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

    public ProfileViewModel(IOrderService orderService)  
    {  
        _orderService = orderService;  
    }  
    ...  
}

ProfileViewModel КонструкторIOrderService получает экземпляр в качестве аргумента, который внедряется другим классом.The ProfileViewModel constructor receives an IOrderService instance as an argument, injected by another class. Единственная зависимость в ProfileViewModel классе относится к типу интерфейса.The only dependency in the ProfileViewModel class is on the interface type. Таким образом, ProfileViewModel класс не имеет сведений о классе, который отвечает за создание экземпляра IOrderService объекта.Therefore, the ProfileViewModel class doesn't have any knowledge of the class that's responsible for instantiating the IOrderService object. Класс, отвечающий за создание экземпляра IOrderService объекта и его вставку ProfileViewModel в класс, называется контейнером внедрения зависимостей.The class that's responsible for instantiating the IOrderService object, and inserting it into the ProfileViewModel class, is known as the dependency injection container.

Контейнеры внедрения зависимостей уменьшают взаимосвязь между объектами, предоставляя возможность создавать экземпляры классов и управлять временем существования в зависимости от конфигурации контейнера.Dependency injection containers reduce the coupling between objects by providing a facility to instantiate class instances and manage their lifetime based on the configuration of the container. Во время создания объектов контейнер внедряет все зависимости, необходимые объекту.During the objects creation, the container injects any dependencies that the object requires into it. Если эти зависимости еще не созданы, контейнер сначала создает и разрешает их зависимости.If those dependencies have not yet been created, the container creates and resolves their dependencies first.

Примечание

Внедрение зависимостей также может быть реализовано вручную с помощью фабрик.Dependency injection can also be implemented manually using factories. Однако использование контейнера предоставляет дополнительные возможности, такие как управление жизненным циклом и регистрация через сканирование сборок.However, using a container provides additional capabilities such as lifetime management, and registration through assembly scanning.

Использование контейнера внедрения зависимостей имеет несколько преимуществ.There are several advantages to using a dependency injection container:

  • Контейнер устраняет необходимость класса для определения его зависимостей и управления временем существования.A container removes the need for a class to locate its dependencies and manage their lifetimes.
  • Контейнер позволяет сопоставлять реализованные зависимости, не влияя на класс.A container allows mapping of implemented dependencies without affecting the class.
  • Контейнер упрощает тестирование, позволяя макетирование зависимостей.A container facilitates testability by allowing dependencies to be mocked.
  • Контейнер повышает удобство поддержки, позволяя легко добавлять новые классы в приложение.A container increases maintainability by allowing new classes to be easily added to the app.

В контексте приложения Xamarin. Forms, использующего MVVM, контейнер внедрения зависимостей обычно используется для регистрации и разрешения моделей представлений, а также для регистрации служб и их внедрения в модели представления.In the context of a Xamarin.Forms app that uses MVVM, a dependency injection container will typically be used for registering and resolving view models, and for registering services and injecting them into view models.

Существует множество доступных контейнеров внедрения зависимостей с помощью мобильного приложения eShopOnContainers, использующего Autofac для управления созданием экземпляров модели представления и классов служб в приложении.There are many dependency injection containers available, with the eShopOnContainers mobile app using Autofac to manage the instantiation of view model and service classes in the app. Autofac упрощает создание слабо связанных приложений и предоставляет все функции, которые часто обнаруживаются в контейнерах внедрения зависимостей, в том числе методы для регистрации сопоставлений типов и экземпляров объектов, разрешения объектов, управления жизненным циклом объектов и вставки зависимые объекты в конструкторы объектов, которые он разрешает.Autofac facilitates building loosely coupled apps, and provides all of the features commonly found in dependency injection containers, including methods to register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into constructors of objects that it resolves. Дополнительные сведения о Autofac см. в разделе Autofac on readthedocs.IO.For more information about Autofac, see Autofac on readthedocs.io.

В Autofac IContainer интерфейс предоставляет контейнер внедрения зависимостей.In Autofac, the IContainer interface provides the dependency injection container. На рис. 3-1 показаны зависимости при использовании этого контейнера, который создает экземпляр IOrderService объекта и внедряет его ProfileViewModel в класс.Figure 3-1 shows the dependencies when using this container, which instantiates an IOrderService object and injects it into the ProfileViewModel class.

Рис. 3-1. Зависимости при использовании внедрения зависимостейFigure 3-1: Dependencies when using dependency injection

Во время выполнения контейнер должен быть уверен, какую реализацию IOrderService интерфейса он должен создавать, прежде чем он сможет создать экземпляр ProfileViewModel объекта.At runtime, the container must know which implementation of the IOrderService interface it should instantiate, before it can instantiate a ProfileViewModel object. Это включает в себя следующее:This involves:

  • Контейнер, который решает, как создать экземпляр объекта, реализующего IOrderService интерфейс.The container deciding how to instantiate an object that implements the IOrderService interface. Это называется регистрацией.This is known as registration.
  • Контейнер, создающий экземпляр объекта, который реализует IOrderService интерфейс, ProfileViewModel и объект.The container instantiating the object that implements the IOrderService interface, and the ProfileViewModel object. Это называется разрешением.This is known as resolution.

В конечном итоге приложение завершит использование ProfileViewModel объекта и станет доступным для сборки мусора.Eventually, the app will finish using the ProfileViewModel object and it will become available for garbage collection. На этом этапе сборщик мусора должен удалить IOrderService экземпляр, если другие классы не используют один и тот же экземпляр.At this point, the garbage collector should dispose of the IOrderService instance if other classes do not share the same instance.

Совет

Написание кода, независимого от контейнера.Write container-agnostic code. Всегда пытайтесь записать код, не зависящий от контейнера, чтобы отделить приложение от конкретного используемого контейнера зависимостей.Always try to write container-agnostic code to decouple the app from the specific dependency container being used.

РегистрацияRegistration

Прежде чем можно будет внедрить зависимости в объект, необходимо сначала зарегистрировать типы зависимостей в контейнере.Before dependencies can be injected into an object, the types of the dependencies must first be registered with the container. Регистрация типа обычно подразумевает передачу контейнера интерфейс и конкретный тип, реализующий интерфейс.Registering a type typically involves passing the container an interface and a concrete type that implements the interface.

Существует два способа регистрации типов и объектов в контейнере с помощью кода:There are two ways of registering types and objects in the container through code:

  • Зарегистрируйте тип или сопоставление с контейнером.Register a type or mapping with the container. При необходимости контейнер будет строить экземпляр указанного типа.When required, the container will build an instance of the specified type.
  • Зарегистрируйте существующий объект в контейнере как одноэлементный.Register an existing object in the container as a singleton. При необходимости контейнер вернет ссылку на существующий объект.When required, the container will return a reference to the existing object.

Совет

Контейнеры внедрения зависимостей не всегда подходят.Dependency injection containers are not always suitable. Внедрение зависимостей предоставляет дополнительные сложности и требования, которые могут быть нецелесообразными или полезными для небольших приложений.Dependency injection introduces additional complexity and requirements that might not be appropriate or useful to small apps. Если у класса нет зависимостей или он не зависит от других типов, он может не иметь смысла размещать его в контейнере.If a class does not have any dependencies, or is not a dependency for other types, it might not make sense to put it in the container. Кроме того, если класс содержит один набор зависимостей, которые являются неотъемлемой частью типа и никогда не изменяются, может не иметь смысла размещать его в контейнере.In addition, if a class has a single set of dependencies that are integral to the type and will never change, it might not make sense to put it in the container.

Регистрация типов, требующих внедрения зависимостей, должна выполняться в одном методе в приложении, и этот метод должен вызываться на раннем этапе жизненного цикла приложения, чтобы гарантировать, что приложение знает о зависимостях между его классами.The registration of types that require dependency injection should be performed in a single method in an app, and this method should be invoked early in the app's lifecycle to ensure that the app is aware of the dependencies between its classes. В мобильном приложении eShopOnContainers это выполняется ViewModelLocator классом, который IContainer создает объект и является единственным классом в приложении, который содержит ссылку на этот объект.In the eShopOnContainers mobile app this is performed by the ViewModelLocator class, which builds the IContainer object and is the only class in the app that holds a reference to that object. В следующем примере кода показано, как мобильное приложение eShopOnContainers объявляет IContainer объект ViewModelLocator в классе:The following code example shows how the eShopOnContainers mobile app declares the IContainer object in the ViewModelLocator class:

private static IContainer _container;

Типы и экземпляры регистрируются в RegisterDependencies методе ViewModelLocator класса.Types and instances are registered in the RegisterDependencies method in the ViewModelLocator class. Для этого сначала создается ContainerBuilder экземпляр, который демонстрируется в следующем примере кода:This is achieved by first creating a ContainerBuilder instance, which is demonstrated in the following code example:

var builder = new ContainerBuilder();

Затем типы и экземпляры регистрируются ContainerBuilder в объекте, а в следующем примере кода демонстрируется наиболее распространенная форма регистрации типа:Types and instances are then registered with the ContainerBuilder object, and the following code example demonstrates the most common form of type registration:

builder.RegisterType<RequestProvider>().As<IRequestProvider>();

Метод RegisterType , показанный здесь, сопоставляет тип интерфейса с конкретным типом.The RegisterType method shown here maps an interface type to a concrete type. Он указывает контейнеру создать экземпляр RequestProvider объекта при создании экземпляра объекта, который требует введения с IRequestProvider помощью конструктора.It tells the container to instantiate a RequestProvider object when it instantiates an object that requires an injection of an IRequestProvider through a constructor.

Конкретные типы также могут быть зарегистрированы напрямую без сопоставления типа интерфейса, как показано в следующем примере кода:Concrete types can also be registered directly without a mapping from an interface type, as shown in the following code example:

builder.RegisterType<ProfileViewModel>();

При разрешении ProfileViewModel типа контейнер будет внедрять необходимые зависимости.When the ProfileViewModel type is resolved, the container will inject its required dependencies.

Autofac также позволяет регистрировать экземпляры, где контейнер отвечает за поддержание ссылки на одноэлементный экземпляр типа.Autofac also allows instance registration, where the container is responsible for maintaining a reference to a singleton instance of a type. Например, в следующем примере кода показано, как мобильное приложение eShopOnContainers регистрирует конкретный тип для использования, когда ProfileViewModel экземпляру IOrderService требуется экземпляр:For example, the following code example shows how the eShopOnContainers mobile app registers the concrete type to use when a ProfileViewModel instance requires an IOrderService instance:

builder.RegisterType<OrderService>().As<IOrderService>().SingleInstance();

Метод RegisterType , показанный здесь, сопоставляет тип интерфейса с конкретным типом.The RegisterType method shown here maps an interface type to a concrete type. SingleInstance Метод настраивает регистрацию таким образом, чтобы каждый зависимый объект получал один и тот же общий экземпляр.The SingleInstance method configures the registration so that every dependent object receives the same shared instance. Таким образом, в контейнере будет существовать только один OrderService экземпляр, который совместно используется объектами, требующими введения IOrderService с помощью конструктора.Therefore, only a single OrderService instance will exist in the container, which is shared by objects that require an injection of an IOrderService through a constructor.

Регистрацию экземпляра также можно выполнить с помощью RegisterInstance метода, как показано в следующем примере кода:Instance registration can also be performed with the RegisterInstance method, which is demonstrated in the following code example:

builder.RegisterInstance(new OrderMockService()).As<IOrderService>();

Показанный здесь OrderMockService метод создает новый экземпляр и регистрирует его в контейнере. RegisterInstanceThe RegisterInstance method shown here creates a new OrderMockService instance and registers it with the container. Таким образом, в контейнере существует только один OrderMockService экземпляр, который совместно используется объектами, требующими введения IOrderService с помощью конструктора.Therefore, only a single OrderMockService instance exists in the container, which is shared by objects that require an injection of an IOrderService through a constructor.

После регистрации IContainer типа и экземпляра объект должен быть построен, что демонстрируется в следующем примере кода:Following type and instance registration, the IContainer object must be built, which is demonstrated in the following code example:

_container = builder.Build();

При вызове Build метода в экземпляресоздаетсяновыйконтейнервнедрениязависимостей,которыйсодержитContainerBuilder выполненные регистрации.Invoking the Build method on the ContainerBuilder instance builds a new dependency injection container that contains the registrations that have been made.

Совет

IContainer Рассмотрим как неизменяемый.Consider an IContainer as being immutable. Хотя Autofac предоставляет Update метод для обновления регистраций в существующем контейнере, по возможности следует избегать вызова этого метода.While Autofac provides an Update method to update registrations in an existing container, calling this method should be avoided where possible. Существует несколько рисков по изменению контейнера после его сборки, особенно если контейнер используется.There are risks to modifying a container after it's been built, particularly if the container has been used. Дополнительные сведения см. в разделе рассмотрение контейнера как неизменяемого в readthedocs.IO.For more information, see Consider a Container as Immutable on readthedocs.io.

РешениеResolution

После регистрации типа его можно разрешить или внедрить как зависимость.After a type is registered, it can be resolved or injected as a dependency. Если тип разрешается и контейнеру необходимо создать новый экземпляр, он внедряет все зависимости в экземпляр.When a type is being resolved and the container needs to create a new instance, it injects any dependencies into the instance.

Как правило, при разрешении типа происходит одно из трех действий:Generally, when a type is resolved, one of three things happens:

  1. Если тип не был зарегистрирован, контейнер создает исключение.If the type hasn't been registered, the container throws an exception.
  2. Если тип был зарегистрирован как одноэлементный, контейнер возвращает одноэлементный экземпляр.If the type has been registered as a singleton, the container returns the singleton instance. Если этот тип вызывается в первый раз, контейнер создает его при необходимости и сохраняет ссылку на него.If this is the first time the type is called for, the container creates it if required, and maintains a reference to it.
  3. Если тип не был зарегистрирован как singleton, контейнер возвращает новый экземпляр и не поддерживает ссылку на него.If the type hasn't been registered as a singleton, the container returns a new instance, and doesn't maintain a reference to it.

В следующем примере кода показано, как RequestProvider можно разрешить тип, который был ранее зарегистрирован в Autofac:The following code example shows how the RequestProvider type that was previously registered with Autofac can be resolved:

var requestProvider = _container.Resolve<IRequestProvider>();

В этом примере Autofac запрос на разрешение конкретного типа для IRequestProvider типа вместе с любыми зависимостями.In this example, Autofac is asked to resolve the concrete type for the IRequestProvider type, along with any dependencies. Как правило, Resolve метод вызывается, когда требуется экземпляр определенного типа.Typically, the Resolve method is called when an instance of a specific type is required. Сведения об управлении временем существования разрешенных объектов см. в разделе Управление временем существования разрешенных объектов.For information about controlling the lifetime of resolved objects, see Managing the Lifetime of Resolved Objects.

В следующем примере кода показано, как мобильное приложение eShopOnContainers создает экземпляры типов моделей представления и их зависимостей:The following code example shows how the eShopOnContainers mobile app instantiates view model types and their dependencies:

var viewModel = _container.Resolve(viewModelType);

В этом примере Autofac запрос на разрешение типа модели представления для запрошенной модели представления, и контейнер также будет разрешать все зависимости.In this example, Autofac is asked to resolve the view model type for a requested view model, and the container will also resolve any dependencies. При разрешении ProfileViewModel типа зависимости для разрешения IOrderService — это объект.When resolving the ProfileViewModel type, the dependency to resolve is an IOrderService object. Таким образом, Autofac сначала конструирует OrderService объект, а затем передает его конструктору ProfileViewModel класса.Therefore, Autofac first constructs an OrderService object and then passes it to the constructor of the ProfileViewModel class. Дополнительные сведения о том, как конструкция eShopOnContainers Mobile App формирует модели представления и связывает их с представлениями, см. в разделе Автоматическое создание модели представления с указателем модели представления.For more information about how the eShopOnContainers mobile app constructs view models and associates them to views, see Automatically Creating a View Model with a View Model Locator.

Примечание

Регистрация и разрешение типов в контейнере влечет затраты с точки зрения производительности из-за использования отражения в контейнере для создания каждого типа, особенно если зависимости перестраиваются при каждом переходе по страницам в приложении.Registering and resolving types with a container has a performance cost because of the container's use of reflection for creating each type, especially if dependencies are being reconstructed for each page navigation in the app. При наличии большого числа зависимостей или глубоких зависимостей стоимость создания может значительно возрасти.If there are many or deep dependencies, the cost of creation can increase significantly.

Управление временем существования разрешенных объектовManaging the Lifetime of Resolved Objects

После регистрации типа поведением по умолчанию для Autofac является создание нового экземпляра зарегистрированного типа каждый раз, когда тип разрешается, или когда механизм зависимости внедряет экземпляры в другие классы.After registering a type, the default behavior for Autofac is to create a new instance of the registered type each time the type is resolved, or when the dependency mechanism injects instances into other classes. В этом сценарии контейнер не хранит ссылку на разрешенный объект.In this scenario, the container doesn't hold a reference to the resolved object. Однако при регистрации экземпляра поведением по умолчанию для Autofac является управление временем существования объекта в виде одноэлементного элемента.However, when registering an instance, the default behavior for Autofac is to manage the lifetime of the object as a singleton. Таким образом, экземпляр остается в области, пока контейнер находится в области, и удаляется, когда контейнер выходит из области действия и уничтожается сборщиком мусора, или когда код явным образом удаляет контейнер.Therefore, the instance remains in scope while the container is in scope, and is disposed when the container goes out of scope and is garbage collected, or when code explicitly disposes the container.

Область экземпляра Autofac можно использовать для указания одноэлементного поведения для объекта, который Autofac создает из зарегистрированного типа.An Autofac instance scope can be used to specify the singleton behavior for an object that Autofac creates from a registered type. Области экземпляра Autofac управляют временем существования объектов, создаваемых контейнером.Autofac instance scopes manage the object lifetimes instantiated by the container. Областью экземпляра по умолчанию RegisterType для метода InstancePerDependency является область.The default instance scope for the RegisterType method is the InstancePerDependency scope. Однако область можно использовать RegisterType с методом, чтобы контейнер создавал или возвращал одноэлементный экземпляр типа при вызове Resolve метода. SingleInstanceHowever, the SingleInstance scope can be used with the RegisterType method, so that the container creates or returns a singleton instance of a type when calling the Resolve method. В следующем примере кода показано, как Autofac указывает, как создать одноэлементный экземпляр NavigationService класса:The following code example shows how Autofac is instructed to create a singleton instance of the NavigationService class:

builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();

При INavigationService первом разрешении интерфейса контейнер создает новый NavigationService объект и сохраняет ссылку на него.The first time that the INavigationService interface is resolved, the container creates a new NavigationService object and maintains a reference to it. При любом последующем разрешении INavigationService интерфейса контейнер возвращает ссылку NavigationService на объект, который был создан ранее.On any subsequent resolutions of the INavigationService interface, the container returns a reference to the NavigationService object that was previously created.

Примечание

Область Синглеинстанце удаляет созданные объекты при удалении контейнера.The SingleInstance scope disposes created objects when the container is disposed.

Autofac включает дополнительные области экземпляров.Autofac includes additional instance scopes. Дополнительные сведения см. в разделе scope of instance on readthedocs.IO.For more information, see Instance Scope on readthedocs.io.

СводкаSummary

Внедрение зависимостей позволяет разделять конкретные типы от кода, который зависит от этих типов.Dependency injection enables decoupling of concrete types from the code that depends on these types. Обычно используется контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.It typically uses a container that holds a list of registrations and mappings between interfaces and abstract types, and the concrete types that implement or extend these types.

Autofac упрощает создание слабо связанных приложений и предоставляет все функции, которые часто обнаруживаются в контейнерах внедрения зависимостей, в том числе методы для регистрации сопоставлений типов и экземпляров объектов, разрешения объектов, управления жизненным циклом объектов и вставки зависимые объекты в конструкторы объектов, которые он разрешает.Autofac facilitates building loosely coupled apps, and provides all of the features commonly found in dependency injection containers, including methods to register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into constructors of objects it resolves.