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

Примечание.

Эта электронная книга была опубликована весной 2017 года и с тех пор не была обновлена. Есть много в книге, которая остается ценным, но некоторые из материалов устарели.

Как правило, конструктор класса вызывается при создании экземпляра объекта и все значения, необходимые объекту, передаются в качестве аргументов конструктору. Это пример внедрения зависимостей и, в частности, называется внедрением конструктора. Зависимости, необходимые для объекта, внедряются в конструктор.

При указании зависимостей в качестве типов интерфейса внедрение зависимостей позволяет развязывание конкретных типов от кода, зависящее от этих типов. Обычно он использует контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.

Существуют и другие типы внедрения зависимостей, такие как внедрение набора свойств, и внедрение вызовов методов, но они реже встречаются. Поэтому эта глава будет сосредоточена исключительно на выполнении внедрения конструктора с контейнером внедрения зависимостей.

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

Внедрение зависимостей — это специализированная версия шаблона инверсии элемента управления (IoC), при которой превратимая проблема — это процесс получения требуемой зависимости. При внедрении зависимостей другой класс отвечает за внедрение зависимостей в объект во время выполнения. В следующем примере кода показано, как ProfileViewModel класс структурирован при использовании внедрения зависимостей:

public class ProfileViewModel : ViewModelBase  
{  
    private IOrderService _orderService;  

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

Конструктор ProfileViewModel получает IOrderService экземпляр в качестве аргумента, внедренного другим классом. Единственная зависимость в ProfileViewModel классе зависит от типа интерфейса. Таким образом, ProfileViewModel класс не имеет никаких знаний о классе, ответственном за создание экземпляра IOrderService объекта. Класс, отвечающий за создание экземпляра IOrderService объекта и его вставку в ProfileViewModel класс, называется контейнером внедрения зависимостей.

Контейнеры внедрения зависимостей сокращают связь между объектами, предоставляя объект для создания экземпляров классов и управления их временем существования на основе конфигурации контейнера. Во время создания объектов контейнер внедряет в него все зависимости, необходимые объекту. Если эти зависимости еще не созданы, контейнер сначала создает и разрешает их зависимости.

Примечание.

Внедрение зависимостей также можно реализовать вручную с помощью фабрик. Однако использование контейнера предоставляет дополнительные возможности, такие как управление временем существования и регистрация с помощью сканирования сборок.

Существует несколько преимуществ использования контейнера внедрения зависимостей:

  • Контейнер удаляет необходимость в поиске зависимостей класса и управлении их временем существования.
  • Контейнер позволяет сопоставлять реализованные зависимости, не затрагивая класс.
  • Контейнер упрощает тестирование, позволяя издеваться над зависимостями.
  • Контейнер повышает удобство обслуживания, позволяя новым классам легко добавляться в приложение.

В контексте Xamarin.Forms приложения, использующего MVVM, контейнер внедрения зависимостей обычно будет использоваться для регистрации и разрешения моделей представлений, а также для регистрации служб и внедрения их в модели просмотра.

Существует множество контейнеров внедрения зависимостей с помощью мобильного приложения eShopOnContainers с помощью TinyIoC для управления экземпляром модели представления и классов служб в приложении. TinyIoC был выбран после оценки ряда различных контейнеров и имеет более высокую производительность на мобильных платформах по сравнению с большинством известных контейнеров. Он упрощает создание слабо связанных приложений и предоставляет все функции, часто найденные в контейнерах внедрения зависимостей, включая методы для регистрации сопоставлений типов, разрешения объектов, управления временем существования объектов и внедрения зависимых объектов в конструкторы объектов, которые он разрешает. Дополнительные сведения о TinyIoC см. в разделе TinyIoC на github.com.

В TinyIoC TinyIoCContainer тип предоставляет контейнер внедрения зависимостей. На рисунке 3-1 показаны зависимости при использовании этого контейнера, который создает экземпляр IOrderService объекта и внедряет его в ProfileViewModel класс.

Dependencies example when using dependency injection

Рис. 3-1. Зависимости при использовании внедрения зависимостей

Во время выполнения контейнер должен знать, какую реализацию IOrderService интерфейса он должен создать, прежде чем он сможет создать ProfileViewModel экземпляр объекта. Для этого необходимо выполнить указанные ниже действия.

  • Контейнер, определяющий, как создать экземпляр объекта, реализующего IOrderService интерфейс. Это называется регистрацией.
  • Контейнер, создающий экземпляр объекта, реализующего IOrderService интерфейс, и ProfileViewModel объект. Это называется разрешением.

В конечном итоге приложение завершит использование ProfileViewModel объекта и станет доступным для сборки мусора. На этом этапе сборщик мусора должен удалить IOrderService экземпляр, если другие классы не используют один и тот же экземпляр.

Совет

Напишите не зависящий от контейнера код. Всегда старайтесь писать не зависящий от контейнера код, чтобы отделить приложение от используемого контейнера зависимостей.

Регистрация

Перед внедрением зависимостей в объект необходимо сначала зарегистрировать типы зависимостей в контейнере. Регистрация типа обычно включает передачу контейнера интерфейса и конкретного типа, реализующего интерфейс.

Существует два способа регистрации типов и объектов в контейнере с помощью кода:

  • Зарегистрируйте тип или сопоставление с контейнером. При необходимости контейнер создаст экземпляр указанного типа.
  • Зарегистрируйте существующий объект в контейнере в качестве одного. При необходимости контейнер вернет ссылку на существующий объект.

Совет

Контейнеры внедрения зависимостей не всегда подходят. Внедрение зависимостей представляет дополнительную сложность и требования, которые могут быть не подходящими или полезными для небольших приложений. Если класс не имеет зависимостей или не является зависимостью для других типов, он может не иметь смысла поместить его в контейнер. Кроме того, если класс имеет один набор зависимостей, которые являются неотъемлемой частью типа и никогда не изменятся, он может не иметь смысла поместить его в контейнер.

Регистрация типов, требующих внедрения зависимостей, должна выполняться в одном методе в приложении, и этот метод должен вызываться в начале жизненного цикла приложения, чтобы убедиться, что приложение знает о зависимостях между его классами. В мобильном приложении eShopOnContainers это выполняется классом ViewModelLocator , который создает TinyIoCContainer объект и является единственным классом в приложении, которое содержит ссылку на этот объект. В следующем примере кода показано, как мобильное приложение eShopOnContainers объявляет TinyIoCContainer объект в ViewModelLocator классе:

private static TinyIoCContainer _container;

Типы регистрируются в конструкторе ViewModelLocator . Это достигается путем первого создания экземпляра TinyIoCContainer , который демонстрируется в следующем примере кода:

_container = new TinyIoCContainer();

Затем типы регистрируются в объекте TinyIoCContainer , и в следующем примере кода демонстрируется наиболее распространенная форма регистрации типов:

_container.Register<IRequestProvider, RequestProvider>();

Приведенный Register здесь метод сопоставляет тип интерфейса с конкретным типом. По умолчанию каждая регистрация интерфейса настраивается как одинтон, чтобы каждый зависимый объект получил один и тот же общий экземпляр. Таким образом, в контейнере будет существовать только один RequestProvider экземпляр, который используется объектами, для которых требуется внедрение IRequestProvider через конструктор.

Конкретные типы также могут быть зарегистрированы непосредственно без сопоставления из типа интерфейса, как показано в следующем примере кода:

_container.Register<ProfileViewModel>();

По умолчанию каждая регистрация конкретного класса настраивается в виде нескольких экземпляров, чтобы каждый зависимый объект получил новый экземпляр. Поэтому при ProfileViewModel разрешении создается новый экземпляр и контейнер внедряет необходимые зависимости.

Разрешение

После регистрации типа его можно разрешить или внедрить как зависимость. При разрешении типа и контейнеру необходимо создать новый экземпляр, он внедряет все зависимости в экземпляр.

Как правило, при разрешении типа происходит одна из трех вещей:

  1. Если тип не зарегистрирован, контейнер создает исключение.
  2. Если тип зарегистрирован в качестве одного, контейнер возвращает одноэлементный экземпляр. Если этот тип вызывается при первом вызове, контейнер создает его при необходимости и сохраняет ссылку на него.
  3. Если тип не зарегистрирован в качестве единого, контейнер возвращает новый экземпляр и не сохраняет ссылку на него.

В следующем примере кода показано, как RequestProvider можно разрешить тип, который ранее зарегистрирован в TinyIoC:

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

В этом примере TinyIoC запрашивает разрешение конкретного IRequestProvider типа для типа, а также любых зависимостей. Как правило, Resolve метод вызывается, когда требуется экземпляр определенного типа. Сведения об управлении временем существования разрешенных объектов см. в разделе "Управление временем существования разрешенных объектов".

В следующем примере кода показано, как мобильное приложение eShopOnContainers создает экземпляры типов моделей представления и их зависимостей:

var viewModel = _container.Resolve(viewModelType);

В этом примере TinyIoC запрашивает разрешение типа модели представления для запрошенной модели представления, а контейнер также разрешает все зависимости. При разрешении ProfileViewModel типа зависимости для разрешения являются ISettingsService объектом и IOrderService объектом. Так как при регистрации SettingsService и OrderService классах были использованы регистрации интерфейсов, TinyIoC возвращает однотонные экземпляры для SettingsService и классов, OrderService а затем передает их конструктору ProfileViewModel класса. Дополнительные сведения о создании моделей просмотра мобильных приложений eShopOnContainers и их связывании с представлениями см. в статье "Автоматическое создание модели представления с помощью указателя модели представления".

Примечание.

Регистрация и разрешение типов в контейнере влечет затраты с точки зрения производительности из-за использования отражения в контейнере для создания каждого типа, особенно если зависимости перестраиваются при каждом переходе по страницам в приложении. При наличии большого числа зависимостей или глубоких зависимостей стоимость создания может значительно возрасти.

Управление временем существования разрешенных объектов

После регистрации типа с помощью конкретной регистрации класса поведение по умолчанию для TinyIoC заключается в создании нового экземпляра зарегистрированного типа при каждом разрешении типа или при внедрении экземпляров в другие классы механизма зависимостей. В этом сценарии контейнер не содержит ссылку на разрешенный объект. Однако при регистрации типа с помощью регистрации интерфейса поведение по умолчанию для TinyIoC заключается в управлении временем существования объекта в качестве единого. Поэтому экземпляр остается в область во время область контейнера и удаляется, когда контейнер выходит из область и собирает мусор или когда код явно удаляет контейнер.

Поведение регистрации TinyIoC по умолчанию можно переопределить с помощью методов fluent AsSingleton и AsMultiInstance API. Например, AsSingleton метод можно использовать с Register методом, чтобы контейнер создал или возвращает одиночный экземпляр типа при вызове Resolve метода. В следующем примере кода показано, как Будет показано, как TinyIoC будет создан одиночный экземпляр LoginViewModel класса:

_container.Register<LoginViewModel>().AsSingleton();

При первом LoginViewModel разрешении типа контейнер создает новый LoginViewModel объект и сохраняет ссылку на него. При любых последующих разрешениях LoginViewModelконтейнера возвращает ссылку на LoginViewModel созданный ранее объект.

Примечание.

Типы, зарегистрированные как однотонные, удаляются при удалении контейнера.

Итоги

Внедрение зависимостей позволяет разбиение конкретных типов из кода, зависящее от этих типов. Обычно он использует контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.

TinyIoC — это упрощенный контейнер, который имеет более высокую производительность на мобильных платформах по сравнению с большинством известных контейнеров. Он упрощает создание слабо связанных приложений и предоставляет все функции, часто найденные в контейнерах внедрения зависимостей, включая методы для регистрации сопоставлений типов, разрешения объектов, управления временем существования объектов и внедрения зависимых объектов в конструкторы объектов, которые он разрешает.