Внедрение зависимостей
Примечание.
Эта электронная книга была опубликована весной 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
класс.
Рис. 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
разрешении создается новый экземпляр и контейнер внедряет необходимые зависимости.
Разрешение
После регистрации типа его можно разрешить или внедрить как зависимость. При разрешении типа и контейнеру необходимо создать новый экземпляр, он внедряет все зависимости в экземпляр.
Как правило, при разрешении типа происходит одна из трех вещей:
- Если тип не зарегистрирован, контейнер создает исключение.
- Если тип зарегистрирован в качестве одного, контейнер возвращает одноэлементный экземпляр. Если этот тип вызывается при первом вызове, контейнер создает его при необходимости и сохраняет ссылку на него.
- Если тип не зарегистрирован в качестве единого, контейнер возвращает новый экземпляр и не сохраняет ссылку на него.
В следующем примере кода показано, как 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 — это упрощенный контейнер, который имеет более высокую производительность на мобильных платформах по сравнению с большинством известных контейнеров. Он упрощает создание слабо связанных приложений и предоставляет все функции, часто найденные в контейнерах внедрения зависимостей, включая методы для регистрации сопоставлений типов, разрешения объектов, управления временем существования объектов и внедрения зависимых объектов в конструкторы объектов, которые он разрешает.