Главная страница   /   14.1. Знакомство с Unity (Внедрение зависимостей в .NET

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

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

Марк Симан

14.1. Знакомство с Unity

Из этого раздела вы узнаете, где можно взять Unity, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурирования, а также то, как пакетировать настройки конфигурации в повторно используемые компоненты. В таблице 14-1 содержится основополагающая информация, которая, скорее всего, понадобится вам для того, чтобы приступить к работе с Unity.

Таблица 14-1: Краткая информация об Unity
Вопрос Ответ
Откуда мне его получить?

Лучше всего начать с сайта http://unity.codeplex.com/. На главной странице приведены ссылки на самые последние релизы, которые обычно перенаправляют пользователей на веб-узел Microsoft Download Center.

Из Visual Studio 2010 можно получить его посредством NuGet. Имя пакета – Unity.

Что находится в загруженном файле? В отличие от других контейнеров Unity загружается в виде .msi файла. После его установки создаются ярлыки в меню Пуск, а бинарные файлы и исходный код помещаются в папку Program Files. Но поскольку у вас теперь есть бинарные файлы, вы можете развернуть их с помощью Xcopy, если предпочитаете эту форму.
Какие платформы поддерживаются? .NET 3.5 SP1 и .NET 4.0. Silverlight 3 и 4.
Сколько он стоит? Нисколько. По сути Unity – это программное обеспечение с открытым исходным кодом, несмотря на то, что группа Patterns&Practices не принимает внесенные в исходный код изменения.
Откуда мне получить помощь? Unity – это предложение группы Patterns&Practices, а не продукт компании Microsoft. В связи с этим он не сопровождается компанией Microsoft, но зато существует довольно оживленный дискуссионный форум – http://unity.codeplex.com/discussions.
На какой версии Unity основана эта глава? 2.0.

Как и при работе с Castle Windsor и StructureMap, при использовании Unity соблюдается простая цикличность, проиллюстрированная на рисунке 14-2.

Рисунок 14-2: Сначала конфигурируется контейнер, а затем из него разрешаются компоненты. В большинстве случаев создается экземпляр класса UnityContainer, который полностью конфигурируется перед началом разрешения компонентов. Компоненты разрешаются из того же экземпляра, который перед этим конфигурировался.

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

Разрешение объектов

Основная услуга, предоставляемая любым DI-контейнером – разрешение компонентов. В этом разделе мы рассмотрим API, которое позволяет разрешать компоненты с помощью Unity.

Из предыдущих глав вы узнали, что при работе с некоторыми контейнерами (например, Castle Windsor) перед тем, как начать разрешать компоненты, необходимо обязательно явным образом сконфигурировать все компоненты с помощью этого контейнера. В то же время остальные контейнеры (например, StructureMap) полностью понимают, как автоматически интегрировать запрашиваемые компоненты, поскольку они являются конкретными типами с открытыми конструкторами. В сущности, можно сказать, что Unity распознал последнюю из указанных выше категорий, будучи первым DI-контейнером, поддерживающим эту возможность, поэтому самым простым возможным способом использования контейнера Unity является следующий:

var container = new UnityContainer();
SauceBéarnaise sauce = container.Resolve<SauceBéarnaise>();

Благодаря экземпляру UnityContainer generic-метод Resolve можно использовать для получения экземпляра конкретного класса SauceBéarnaise. Поскольку класс SauceBéarnaise имеет конструктор по умолчанию, Unity автоматически понимает, как создавать экземпляр этого класса. Никакого явного конфигурирования контейнера не требуется.

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

Рассмотрим в качестве примера конструктор Mayonnaise:

public Mayonnaise(EggYolk eggYolk, OliveOil oil)

Несмотря на то, что рецепт приготовления майонеза слегка упрощен, и EggYolk, и OliveOil являются конкретными классами, обладающими конструкторами по умолчанию. Сам Mayonnaise не обладает конструктором по умолчанию, но Unity все равно может создавать его без предварительной конфигурации:

var container = new UnityContainer();
var mayo = container.Resolve<Mayonnaise>();

Это возможно, поскольку Unity способен разгадать, как создавать все необходимые параметры конструктора. Но как только мы ввели слабое связывание, мы должны сконфигурировать Unity, преобразовав абстракции в конкретные типы.

Преобразование абстракций в конкретные типы

Несмотря на то, что способность Unity автоматически интегрировать конкретные классы может, наверняка, время от времени быть полезной, для слабого связывания обычно бывает необходимо преобразовывать абстракции к конкретным типам. Создание экземпляров на основании таких преобразований – ключевая услуга, предлагаемая любым DI-контейнером, но вам все равно необходимо задать соответствующее преобразование.

В приведенном ниже примере вы преобразуете интерфейс IIngredient в конкретный класс SauceBéarnaise, который позволяет успешно разрешать IIngredient:

var container = new UnityContainer();
container.RegisterType<IIngredient, SauceBéarnaise>();
IIngredient ingredient = container.Resolve<IIngredient>();

Generic-метод RegisterType – один из тех нескольких методов расширения, которые вызывают слабо типизированный метод RegisterType, заданный для IUnityContainer. В предыдущем примере вы использовали перегрузку, в которой определяли абстракцию, а также конкретный тип как два типовых generic-аргумента. В этом примере вы преобразуете IIngredient в SauceBéarnaise таким образом, чтобы при дальнейшем разрешении IIngredient вы получали экземпляр SauceBéarnaise.

Generic-метод расширения RegisterType помогает предотвратить появление ошибок конфигурации, поскольку конечный тип обладает generic-ограничителем, который задает условие, согласно которому этот тип наследуется от исходного типа. Код, приведенный в предыдущем примере, компилируется, поскольку SauceBéarnaise реализует IIngredient.

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

Разрешение слабо типизированных сервисов

Иногда у вас нет возможности использовать generic API, поскольку на этапе проектирования вы еще не знаете, какой тип вам понадобится. У вас есть только экземпляр Type, но все же вам хотелось бы получить экземпляр этого типа. Пример такой ситуации описан в разделе 7.2 "Построение ASP.NET MVC приложений", в котором обсуждался ASP.NET класс DefaultControllerFactory. Соответствующий метод приведен в следующем примере:

protected internal virtual IController GetControllerInstance(
	RequestContext requestContext, Type controllerType);

Поскольку у вас имеется только экземпляр Type, вы не можете использовать generic'и, а должны прибегнуть к слабо типизированному API. К счастью, Unity имеет слабо типизированную перегрузку метода Resolve, которая позволяет реализовывать метод GetControllerInstance следующим образом:

return (IController)this.container.Resolve(controllerType);

Слабо типизированная перегрузка метода Resolve позволяет передавать аргумент controllerType напрямую в Unity, но при этом вам необходимо явным образом приводить возвращаемое значение к IController.

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

В предыдущем примере this.container – это экземпляр IUnityContainer. Чтобы можно было разрешать запрашиваемый тип, сначала необходимо сконфигурировать все слабо связанные зависимости. Существует множество способов конфигурирования контейнера Unity, а в следующем разделе приведен обзор наиболее универсальных из этих способов.

Конфигурирование контейнера

Как уже говорилось в разделе 3.2 "Конфигурирование DI-контейнеров", существует несколько концептуально разных способов конфигурирования DI-контейнера. На рисунке 14-3 представлен обзор возможных вариантов.

Рисунок 14-3: Концептуально разные варианты конфигурирования. Технология конфигурирования в коде подразумевает строгую типизированность и имеет тенденцию к явному определению. XML, с другой стороны, предполагает позднее связывание, но тоже склонен к явному определению. Автоматическая регистрация, напротив, полагается на соглашения, которые могут быть и строго типизированными, и более слабо определенными, но в Unity не встроена поддержка механизма автоматической регистрации.

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

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

Конфигурация в коде

В разделе 14.1.1 "Разрешение объектов" уже приводился краткий обзор строго типизированного API конфигурации контейнера Unity. В этом разделе мы рассмотрим его более подробно.

За исключением некоторых моментов вся конфигурация контейнера Unity выполняется в слабо типизированном методе RegisterType, определенном для IUnityContainer:

IUnityContainer RegisterType(Type from, Type to, string name,
	LifetimeManager lifetimeManager,
	params InjectionMember[] injectionMembers);

Помимо этого метода в состав Unity входят еще несколько методов расширений. Некоторые из них являются строго типизированными generic-методами, а некоторые – слабо типизированными методами. В этой главе мы сосредоточим все свое внимание на строго типизированном API. Одним из наиболее часто используемых методов является приведенная ниже перегрузка:

container.RegisterType<IIngredient, SauceBéarnaise>();

В отличие от Castle Windsor или Autofac преобразование IIngredient в SauceBearnaise, приведенное в предыдущем примере, не избавляет нас от разрешения самого SauceBearnaise. И sauce, и ingredient будут соответствующим образом разрешаться в следующем коде:

container.RegisterType<IIngredient, SauceBéarnaise>();
var sauce = container.Resolve<SauceBéarnaise>();
var ingredient = container.Resolve<IIngredient>();

Возможно, из разделов 10.1.2 "Конфигурирование контейнера" и 13.1.2 "Конфигурирование ContainerBuilder" вы помните, что преобразование IIngredient в SauceBearnaise с помощью Castle Windsor или Autofac будет приводить к исчезновению конкретного класса (SauceBearnaise) до тех пор, пока вы не предпримите дополнительные шаги. При работе с Unity никаких дополнительных шагов предпринимать не нужно, поскольку Unity позволяет разрешать и IIngredient, и SauceBearnaise. В большинстве случаев возвращаемые объекты – это экземпляры SauceBearnaise.

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

container.RegisterType<IIngredient, SauceBéarnaise>();
container.RegisterType<ICourse, Course>();

Здесь выполняется преобразование IIngredient в SauceBearnaise и преобразование ICourse в Course. Никаких наложений типов не происходит, поэтому все, что происходит дальше должно быть довольно очевидным. Кроме того, вы можете попытаться зарегистрировать одну и ту же абстракцию несколько раз, но, если вы сделаете это так, как приведено в примере ниже, то произойдет что-то неожиданное:

container.RegisterType<IIngredient, Steak>();
container.RegisterType<IIngredient, SauceBéarnaise>();

Регистрация типа без имени определяет значение по умолчанию для этого типа, но при этом переопределяется предыдущее значение по умолчанию. Конечным результатом этого примера является то, что, если вы будете разрешать IIngredient, то получите экземпляр SauceBearnaise, но регистрация Steak при этом будет уже невозможна. Для типа возможно только одно значение по умолчанию, но зарегистрировать вы можете сколько угодно именованных компонентов. Чтобы использовать Steak в качестве IIngredient по умолчанию, вы можете зарегистрировать SauceBearnaise, присвоив ему имя:

container.RegisterType<IIngredient, Steak>();
container.RegisterType<IIngredient, SauceBéarnaise>("sauce");

Steak остается IIngredient по умолчанию, но, кроме того, вы можете разрешить IIngredient в SauceBearnaise, запросив IIngredient с именем sauce. Чтобы точно указать, как будут интегрироваться зависимости, можно воспользоваться именованными компонентами. К этой теме мы вернемся в разделе 14.3.1.

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

Автоматическая регистрация

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

Рассмотрим библиотеку, которая содержит реализации интерфейса IIngredient. Можно выполнять индивидуальную регистрацию каждого класса, но это приведет к многочисленным схожим вызовам метода RegisterType. Что еще хуже, всякий раз при добавлении новой реализации IIngredient необходимо явно регистрировать ее вместе с контейнером, чтобы она была доступна для использования. Было бы более продуктивно установить, что все реализации IIngredient, найденные в этой сборке, необходимо зарегистрировать.

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

Чтобы просмотреть сборку и зарегистрировать все реализации интерфейса IIngredient, можно воспользоваться комбинацией рефлекционного API .NET и слабо типизированного метода RegisterType, как это продемонстрировано в следующем листинге.

Листинг 14-1: Регистрация всех IIngredient сборки
foreach (var t in typeof(Steak).Assembly.GetExportedTypes())
{
	if (typeof(IIngredient).IsAssignableFrom(t))
	{
		container.RegisterType(typeof(IIngredient), t, t.FullName);
	}
}

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

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

Подсказка

Проект с открытым исходным кодом Unity Auto Registration – одна из попыток определить повторно используемое API, которое позволило бы использовать механизм автоматической регистрации в Unity.

Будь мы более великодушны, мы могли бы сказать, что отсутствие API автоматической регистрации означает для нас отсутствие всяких ограничений. Если мы можем что-то закодировать, значит, мы можем это получить. Если бы мы захотели просмотреть папку на факт наличия в ней сборок или выполнить поиск типов в каждой сборке, то мы даже смогли бы реализовать дополнительную функциональность, при которой можно было бы добавлять в приложение расширения без повторной компиляции основного приложения. Это был бы один из способов реализации позднего связывания. Еще один способ – применение XML-конфигурации.

XML-конфигурация

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

Подсказка

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

Unity подразумевает, что мы размещаем XML-конфигурацию в конфигурационном файле приложения. Для загрузки и интерпретации XML-конфигурации в Unity используется стандартное .NET API конфигурации.

Подсказка

Поддержка контейнером Unity возможности выполнения XML-конфигурации полностью сравнима с аналогичной возможностью других DI-контейнеров. В нем даже присутствует XSD-файл, который применяется для того, чтобы разрешить использовать в Visual Studio контекстную подсказку IntelliSense.

Примечание

Поскольку возможность поддержки контейнером Unity XML-конфигурации реализована в виде отдельной сборки, для использования этой возможности необходимо добавить ссылку на сборку Microsoft.Practices.Unity.Configuration.

После добавления ссылки на сборку Microsoft.Practices.Unity.Configuration мы должны добавить директиву using для пространства имен Microsoft.Practices.Unity.Configuration, чтобы нам стал доступен метод расширения LoadConfiguration. Это позволяет нам загружать XML-конфигурацию посредством всего лишь одного вызова метода:

container.LoadConfiguration();

Метод LoadConfiguration загружает XML-конфигурацию из стандартного конфигурационного файла приложения в контейнер.

Примечание

К сожалению, мы не можем считывать XML из других источников, например, из потоков или XML-узлов, поскольку соответствующее API в Unity отсутствует.

Подсказка

Несмотря на то, что мы не можем считывать XML из произвольных источников, мы можем считывать XML из любого конфигурационного файла с помощью API ConfigurationManager.

Чтобы разрешить выполнять конфигурацию контейнера Unity в конфигурационном файле, мы должны сначала добавить раздел конфигурации, используя стандартное .NET API конфигурации, предназначенное для определения пользовательских разделов конфигурации:

<configSections>
	<section name="unity"
					type="Microsoft.Practices.Unity.Configuration.
					➥UnityConfigurationSection,
					➥Microsoft.Practices.Unity.Configuration"/>
</configSections>

Это API позволяет нам добавить в конфигурационный файл раздел конфигурации unity. Ниже приведен простой пример преобразования интерфейса IIngredient в класс Steak:

<unity>
	<namespace name="Ploeh.Samples.MenuModel" />
	<assembly name="Ploeh.Samples.MenuModel" />
	<container>
		<register type="IIngredient" mapTo="Steak" />
	</container>
</unity>

XML-схема контейнера Unity позволяет определять осмысленные значения по умолчанию, которые, возможно, помогут сократить большие объемы кода, появляющиеся при работе с квалифицированными именами типов сборки в XML. Несмотря на то, что добавлять элементы namespace не обязательно, мы можем добавлять их в неограниченном количестве. Элементы namespace эквивалентны директивам using языка C#. В приведенном выше примере кода мы добавляем только одно пространство имен Ploeh.Samples.MenuModel, но могли бы добавить и большее количество таких элементов или же вовсе их опустить. Если мы опускаем элемент namespace, то все равно можем явным образом передавать полностью квалифицированное имя типа в виде составляющей части регистрации.

Работа с элементом assembly аналогична работе с элементом namespace. Мы можем добавлять неограниченное количество элементов assembly или же совсем их опускать. В приведенном выше примере кода мы добавляем сборку Ploeh.Samples.MenuModel, в которой определяются интерфейс IIngredient и класс Steak.

Это позволяет нам четко устанавливать соответствие между IIngredient и Steak с помощью элемента register. Поскольку мы добавили в контекст пространства имен и сборки, мы можем ссылаться на IIngredient и Steak по их сокращенным именам. Пока имена в рамках контекста четко определены, Unity раскрывает их за нас подобно тому, как это делает компилятор C#.

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

Подсказка

Вы не забыли, что выигрывает последняя конфигурация? Этот факт вы можете использовать для того, чтобы переписать жестко закодированную конфигурацию с помощью XML-конфигурации. Для этого вы должны не забыть загрузить XML-конфигурацию после конфигурации всех остальных компонентов. И наоборот, если у вас имеется конфигурация, которую нельзя переписывать с помощью XML, примените эту конфигурацию после загрузки XML-конфигурации.

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

Пакетирование конфигурации

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

В контейнере Castle Windsor есть инсталлеры, в StructureMap – регистры, а в Autofac – модули. Но в Unity нет ничего похожего. В нем отсутствует интерфейс, который был бы предназначен, главным образом, для пакетирования конфигурации в повторно используемые компоненты. Но зато в нем есть более или менее достаточная замена этим механизмам: расширения контейнера (Container Extensions).

Целью расширения контейнера Unity является не только пакетирование конфигурации в повторно используемые пакеты. Как следует из его названия, расширение контейнера также можно использовать для того, чтобы расширять функциональность контейнера Unity. Например, такая возможность контейнера Unity, как механизм перехвата, реализуется в виде расширения контейнера (это конкретное расширение мы рассмотрим в разделе 14.3.4).

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

Листинг 14-2: Реализация расширения контейнера
public class IngredientExtension : UnityContainerExtension
{
	protected override void Initialize()
	{
		var a = typeof(Steak).Assembly;
		foreach (var t in a.GetExportedTypes())
		{
			if (typeof(IIngredient).IsAssignableFrom(t))
			{
				this.Container.RegisterType(
				typeof(IIngredient), t, t.FullName);
			}
		}
	}
}

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

Единственное функциональное отличие от листинга 14-1 заключается в том, что теперь вместо локальной переменной вы вызываете метод RegisterType унаследованного свойства Container.

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

container.AddNewExtension<IngredientExtension>();

Метод AddNewExtension вызывает метод AddExtension, который вы также можете использовать в тех ситуациях, когда вам необходимо создать модуль вручную:

container.AddExtension(new IngredientExtension());

Эти примеры функционально эквивалентны.

Подсказка

Расширения контейнера Unity позволяют пакетировать и структурировать конфигурационный код вашего контейнера. Даже если расширения разрабатываются совсем не для этих целей, у вас может появиться желание использовать их вместо однострочной конфигурации. Такой подход сделает вашу Composition Root более читабельной.

Благодаря функциональности расширений контейнера для конфигурирования контейнера Unity можно использовать как технологию конфигурирования в коде, так и XML-конфигурацию, и даже реализованный пользователем механизм автоматической регистрации (хотя этот подход занимает больше времени). После того как контейнер сконфигурирован, можно приступить к разрешению сервисов с помощью этого контейнера, что описано в разделе 14.1.1 "Разрешение объектов".

Этот раздел познакомил нас с DI-контейнером Unity и продемонстрировал фундаментальные принципы: как сконфигурировать и впоследствии использовать созданный контейнер для разрешения сервисов. Разрешение сервисов с легкостью выполняется посредством единичного вызова метода Resolve, поэтому вся сложность заключается в конфигурировании контейнера. Конфигурировать контейнер можно несколькими способами, включая императивный код и XML. До настоящего момента мы рассматривали только самое основное API, а более продвинутые вопросы в этом разделе еще не охватывались. Одна из самых важных проблем – это управление жизненным циклом компонентов.