Главная страница

Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница35 из 43
1   ...   31   32   33   34   35   36   37   38   ...   43
();
Метод
RegisterAssemblyTypes возвращает тот же интерфейс, что и метод
RegisterType
, поэтому можно использовать большинство таких же опций конфигурации, что и при использовании метода
RegisterType
. Это поистине значительная возможность, поскольку она означает, что для использования автоматической регистрации не нужно разбираться ни с каким новым API. В предыду щем примере для регистрации всех типов сборки в виде сервисов
IIngredient использовался метод
As
П римечание
Несмотря на то, что типы возвращаемых значений совпадают, возвращаемый методом
RegisterAssemblyTypes интерфейс – это сложный generic-интерфейс. А поскольку большинство используемых нами API реализуются в виде методов расширений, не все методы можно использовать в любой ситуации. Возможность их использования зависит от типа аргументов возвращаемого generic-интерфейса.
В предыдущем примере, безусловно, конфигурируются все реализации интерфейса
IIngredient
, но существует возможность использовать фильтры, которые позволят выбирать только необходимое подмножество реализаций. Ниже приведен пример основанного на соглашениях отбора, при котором добавляются только те классы, чье имя начинается со слова "Sauce":

503 builder.RegisterAssemblyTypes(typeof(Steak).Assembly)
.Where(t => t.Name.StartsWith("Sauce"))
.As();
При регистрации всех типов сборки для определения критерия отбора можно использовать предикат. Единственное отличие от предыду щего примера – это импликация метода
Where
, в котором отбираются только те типы, имена которых начинаются со слова "Sauce". Обратите внимание на то, что это тот же самый синтаксис, который использовался для фильтрации типов и в Castle Windsor, и в StructureMap.
Существует множество других методов, позволяющих задавать различные критерии отбора. Метод
Where позволяет отфильтровывать только те типы, которые совпадают с предикатом, но есть еще метод
Except
, который работает совсем по-другому.
Помимо отбора из сборки соответствующих типов еще одной составляющей автоматической регистрации является определение соответствующего преобразования. В предыдущих примерах использовался метод
As с конкретным интерфейсом с тем, чтобы зарегистрировать все выбранные типы относительно этого интерфейса.
Т ем не менее, иногда бывает необходимо использовать различные соглашения.
Предположим, что вместо интерфейсов мы используем абстрактные базовые классы и собираемся зарегистрировать все типы сборки, имена которых заканчиваются на "Policy".
Для этих целей существует несколько перегрузок метода
As
, включая ту, которая в качестве входного параметра принимает
Func
: builder.RegisterAssemblyTypes(typeof(DiscountPolicy).Assembly)
.Where(t => t.Name.EndsWith("Policy"))
.As(t => t.BaseType);
Блок кода, заданный для метода
As
, будет использоваться для всякого типа, имя которого заканчивается на "Policy". Такой подход гарантирует, что все классы, имеющие суффикс "P olicy" будут зарегистрированы относительно их базового класса таким образом, что при запросе базового класса контейнер будет разрешать его в тип, преобразованный с помощью этого соглашения.
В рамках Autofac достаточно легко выполнять регистрацию на основании соглашений, при этом используется API, которое практически отражает API, раскрываемое единичным методом
RegisterType
П одсказка
RegisterAssemblyTypes считается множественной формой метода
RegisterType
Метод
RegisterAssemblyTypes принимает в качестве параметра массив params
, состоящий из экземпляров
Assembly
, поэтому в одном соглашении можно использовать сколь угодно много сборок. Поиск сборок в папке и применение всех найденных сборок для реализации дополнительной функциональност и, при которой возможно добавлять расширения без повторной компиляции основного приложения – это не надуманная идея.
Это один из способов реализации позднего связывания. Еще один способ реализации позднего связывания – использование XML конфигурации.

504
XML конфигурация
XML конфигурацию хорошо использовать в тех ситуациях, когда должна существовать возможность изменять конфигурацию без повторной компиляции приложения.
П одсказка
Используйте XML конфигурацию только для тех типов, изменить которые вам нужно, не выполняя при этом повторной компиляции приложения. В остальных случаях применяйте автоматическую регистрацию или технологию конфигурирования в коде.
Самый естественный способ использования XML конфигурации – внедрить ее в стандартный конфигурационный файл .NET приложения. Это возможно, но при этом, чтобы изменить конфигурацию Autofac независимо от стандартного .config файла, придется использовать еще и автономный XML-файл. Независимо от выбранного способа используется практически одинаковое API.
П римечание
Поддержка контейнером Autofac XML конфигурации реализована в отдельной сборке, поэтому, чтобы воспользоваться этой возможностью необходимо добавить ссылку на сборку
Autofac.Configuration
После добавления ссылки на
Autofac.Configuration можно отправить
Container.Builder запрос на считывание регистраций компонентов из стандартного
.config файла следующим образом: builder.RegisterModule(new ConfigurationSettingsReader());
Детальное обсуждение модулей Autofac приведено в разделе 13.1.3, а пока все, что вам необходимо знать – это то, что
ConfigurationSettingsReader
– это класс, который отвечает за объединение XML конфигурации с остальными регистрациями, применяемыми к
Container.Builder
. При использовании конструктора по умолчанию происходит автоматическое считывание из раздела конфигурации стандартного конфигурационного файла приложения, но с помощью еще одной перегрузки можно указать и другой XML-файл, из которого будет считываться конфигурация.
П римечание
К сожалению, API, которое позволило бы нам считывать XML из других источников, к примеру, из потоков или узлов, не существует.
Чтобы разместить конфигурацию Autofac в конфигурационном файле, для начала необходимо добавить раздел конфигурации с помощью стандартного .NET API, предназначенного для определения пользовательских разделов конфигурации:




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





В элемент components можно добавить сколько угодно элементов component
. В каждом элементе с помощью атрибута type необходимо задать конкретный тип. Это единственный обязательный атрибут, а для преобразования класса
Steak в
IIngredient можно использовать также необязательный атрибут service
. Тип указывается с помощью полностью квалифицированного имени типа, но, если тип задан в сборке по умолчанию, то имя сборки можно опустить. Атрибут defaultAssembly является необязательным, но при этом довольно значительной возможностью, которая в случае наличия в одной и той же сборке множества типов позволяет избавиться от большого количества определений типов.
XML конфигурацию хорошо использовать, когда необходимо изменить конфигурацию одного или нескольких компонентов без повторной компиляции приложения. Но поскольку XML конфигурация может быть довольно хрупкой, необходимо прибегать к ней только в указанных случаях и использовать для основной части конфигурации контейнера либо автоматическую регистрацию, либо конфигурацию в коде.
П одсказка
Не забыли, что выигрывает последняя конфигурация? Такое поведение можно использовать для переписывания жестко закодированной с помощью XML конфигурации.
Для этого необходимо не забывать считывать XML конфигурацию после завершения конфигурирования любого другого компонента.
В этом разделе, главным образом, рассматривались различные API конфигурации контейнера Autofac. Хотя можно написать один большой блок неструктурированного кода конфигурации, лучше всего разделить конфигурацию на отдельные составляющие.
Autofac поддерживает эту возможность с помощью "модулей".
Пакетирование конфигурации
Иногда существует необходимость упаковать логику конфигурации в повторно используемые группы. И даже в тех случаях, когда само по себе повторное использование не является высшим приоритетом, при конфигурировании больших и сложных приложений может появиться необходимость создать некоторого рода структуру.
При работе с Autofac конфигурацию можно упаковывать в модули. Модуль – это класс, реализующий интерфейс
IModule
, но в большинстве случаев проще всего выполнить наследование от абстрактного класса
Module
. На рисунке 13-4 продемонстрирована иерархия типов.

506
Рисунок 13-4: Повторно используемые конфигурации можно упаковывать в реализации интерфейса
IModule
. Самый простой способ реализации
IModule
– наследование от абстрактного класса
Module
, что и делает
IngredientModule
Все, что делается в дальнейшем, можно выполнить и внутри модуля. В следующем листинге представлен модуль, который регистрирует все реализации
IIngredient
Листинг 13-1: Реализация модуля в Autofac public class IngredientModule : Module
{ protected override void Load(ContainerBuilder builder)
{ var a = typeof(Steak).Assembly; builder.RegisterAssemblyTypes(a).As();
}
}
IngredientModule наследуется от абстрактного класса
Module и переопределяет метод
Load этого класса. Метод
Load
– это метод, определенный классом
Module для облегчения реализации интерфейса
IModule
. Посредством метода
Load вы получаете экземпляр
ContainerBuilder
, который можно использовать для регистрации компонентов таким же самым способом, что и без применения
Module
. Знание способов использования API
ContainerBuilder упрощает реализацию
Module
Чтобы применить
Module
, можно воспользоваться вызовом одной из перегрузок
RegisterModule
. Когда
Module имеет конструктор по умолчанию, можно использовать сокращенную generic-версию: builder.RegisterModule();
Кроме того, существует перегрузка, которая позволяет создать экземпляр, используемый в ситуациях, в которых
Module требуется создавать вручную: builder.RegisterModule(new IngredientModule());
Конфигурировать модули можно и в XML:




507
Эти три примера функционально эквивалентны.
П одсказка
Модули Autofac позволяют пакетировать и структурировать код конфигурации контейнера. Используйте модули вместо однострочной конфигурации, и ваша Com position
Root станет более читабельной.
С помощью модулей конфигурацию контейнера Autofac можно выполнять любым из следующих способов: осуществлять конфигурацию в коде, использовать автоматическую регистрацию или XML. Кроме того, можно сочетать между собой указанные подходы.
После завершения конфигурации контейнера можно отправлять запросы на разрешение сервисов.
Раздел 13.1 "Знакомство с Autofac" познакомил нас с DI-контейнером Autofac и продемонстрировал фундаментальные принципы: как сконфигурировать
ContainerBuilder и впоследствии использовать созданный контейнер для разрешения сервисов. Разрешение сервисов с легкостью выполняется посредством единичного вызова метода
Resolve
, поэтому вся сложность заключается в конфигурировании контейнера.
Конфигурировать контейнер можно несколькими способами, включая императивный код и XML. До настоящего момента мы рассматривали только самое основное API, а более продвинутые вопросы в этом разделе еще не охватывались. Одна из самых важных проблем – управление жизненным циклом компонентов.

508 13.2. Управление жизненным циклом
В главе 8 обсуждался процесс управления жизненным циклом, в том числе наиболее универсальные стили существования, к примеру, Singleton и Transient. Autofac поддерживает несколько стилей существования и позволяет конфигурировать жизненные циклы всех сервисов. Продемонстрированные в таблице 13-2 стили существования являются частью API.
П римечание
В Autofac стили существования называются областя ми применения экземпляров.
Таблица 13-2: Стили существования Autofac
Название
Комментарии
Per
Dependency
Стандартный Transient. Эта область применения экземпляра используется по умолчанию. Отслеживание экземпляров выполняет контейнер.
Single Instance Стандартный Singleton.
Per Lifetime
Scope
Связывает жизненные циклы компонентов с областью применения контейнера (см. раздел 13.2.1).
Contextual
Более расширенная версия Per Lifet ime Scope
Реализации в Autofac стилей существования T ransient и Singleton эквивалентны стилям существования, описанным в главе 8, поэтому в этой главе я не буду тратить время на их рассмотрение.
П одсказка
Используемый по умолчанию стиль Transient – самый безопасный, но не всегда является самым эффективным. Для потоко-безопасных сервисов Singleton – самый эффективный стиль, но вы не должны забывать о явной регистрации этих сервисов.
В этом разделе вы познакомитесь со способами определения стилей существования для компонентов – как с помощью кода, так и при помощи XML. Кроме того, мы рассмотрим сущность област ей применения и то, как их можно использовать для реализации Web
Request Cont ext и других аналогичных стилей существования. После прочтения этого раздела вы уже сможете использовать стили существования Autofac в своем собственном приложении.
Начнем с рассмотрения способов конфигурирования областей применения для компонентов.
Конфигурирование областей применения экземпляров
В этом разделе мы рассмотрим процесс управления областями применения экземпляров с помощью Autofac. Конфигурирование области применения выполняется в рамках регистрации компонентов, а определить ее можно либо с помощью кода, либо при помощи XML. Поочередно рассмотрим каждую из этих возможностей.

509
Конфигурирова ние области применения с помощью кода
Область применения определяется как часть регистраций, которые мы выполняем для экземпляра
ContainerBuilder
. Это столь же просто, как и приведенный ниже код: builder.RegisterType().SingleInstance();
В этом примере конкретный класс
SauceBéarnaise конфигурируется в виде Singleton таким образом, что каждый раз при запросе
SauceBéarnaise возвращается один и тот же экземпляр. Если нам необходимо преобразовать абстракцию в конкретный класс, который имеет конкретный жизненный цикл, то мы можем воспользоваться обычным методом
As и разместить вызов метода
SingleInstance в любом удобном для нас месте. Приведенные ниже регистрации эквивалент ны: builder
.RegisterType()
.As()
.SingleInstance(); builder
.RegisterType()
.SingleInstance()
.As();
Единственное различие между двумя примерами заключается в том, что мы меняем местами вызовы методов
As и
SingleInstance
. Лично я предпочитаю использовать код, продемонстрированный сверху, поскольку вызовы методов
RegisterType и
As формируют преобразование между конкретным классом и абстракцией. Размещение их рядом друг с другом делает регистрацию более читабельной, и после этого мы можем определять область применения как изменение этого преобразования.
Несмотря на то, что стиль Transient используется по умолчанию, мы все равно можем определить его явным образом. Приведенные ниже примеры эквивалентны: builder
.RegisterType(); builder
.RegisterType();
.InstancePerDependency();
Конфигурирование областей применения для регистраций, основанных на соглашениях, выполняется тем же самым методом, который используется и для обычных регистраций: builder.RegisterAssemblyTypes(typeof(Steak).Assembly)
.As()
.SingleInstance();
Чтобы задать область применения для всех регистраций соглашения, можно воспользоваться методом
SingleInstance и другими подходящими методами. В предыдущем примере мы определяли все
IIngredient как Singleton'ы.
Аналогично регистрации компонентов с помощью кода и XML, мы также можем сконфигурировать область применения этими же способами.

510
Конфигурирова ние области применения с помощью XML
При возникновении необходимости определения компонентов с помощью XML вам также захочется иметь возможность конфигурировать в этом же самом месте области применения этих компонентов. Осуществить это можно в виде составляющей части XML- схемы, которую вы уже видели в разделе 13.1.2 "Конфигурирование ContainerBuilder".
Для объявления стиля существования можно использовать необязательный атрибут instance-scope:

Отличие этого примера от примера, приведенного в разделе 13.1.2 "Конфигурирование
ContainerBuilder", заключается в добавленном атрибуте instance-scope
, который конфигурирует экземпляр в виде Singleton. Ранее, когда вы опускали этот атрибут, автоматически использовался стиль Transient.
Как с помощью кода, так и с помощью XML конфигурировать области применения экземпляров для компонентов довольно легко. Во всех случаях выполняется это в слегка декларативной форме. Несмотря на то, что конфигурация выполняется довольно просто, вы не должны забывать о том, что некоторые стили существования содержат объекты- долгожители, которые пользуются ресурсами на протяжении всего своего жизненного цикла.
Высвобожде ние компонентов
Как уже говорилось в разделе 8.2.2 "Управление устраняемыми зависимостями", важно высвободить объекты после завершения работы с ними. Autofac не имеет явного метода
Release
, и вместо него использует так называемые области прим енения жизненного
цикла. Област ь применения жизненного цикла можно рассматривать как неиспользуемую копию контейнера. Как демонстрирует рисунок 13-5, область применения определяет границу, в рамках которой могут повторно использоваться компоненты.

511
Рисунок 13-5: Области применения контейнера Autofac выступают в роли контейнеров, которые могут совместно использовать компоненты в течение ограниченного времени или для ограниченного круга целей. Компонент с ограниченной областью применения – это
Singleton-компонент, расположенный в пределах этой области применения. Независимо от того, сколько раз мы запрашиваем этот компонент, мы получаем один и тот же экземпляр.
Другая область применения содержит свой собственный экземпляр, и родительский контейнер управляет поистине совместно используемыми Singleton-компо нентами.
Transient-компоненты никогда не используются совместно, но их срок действия заканчивается, когда устраняется область применения.
Област ь применения жизненного цикла определяет производный контейнер, который можно использовать для конкретного срока действия или для конкретной цели. Наиболее очевидным примером является веб-запрос. Мы порождаем область применения от контейнера таким образом, что область применения наследует все Singleton'ы, отслеживаемые родительским контейнером, а область применения, помимо этого, играет роль контейнера "локальных Singleton'ов". Когда из области применения запрашивается компонент, относящийся к этой области применения, мы всегда получаем один и тот же экземпляр. Отличие от настоящих Singleton'ов – при запросе второй области применения мы получаем другой экземпляр.
Однако Transient-компоненты функционируют так, как им и положено, независимо от того, разрешаем ли мы их из корневого контейнера или из области применения.

512
П одсказка
Области применения можно использовать для реализации контекстных стилей существования, например, стиля существования W eb Request Context: создайте новую область применения в начале каждого контекста и используйте его для разрешения компонентов. А после завершения запроса устраните область применения. Тем не менее, что касается области применения веб-запроса, Autofac имеет встроенную возможность интеграции как с W eb Forms, так и с ASP.NET MVC, поэтому нам не нужно выполнять эту интеграцию самостоятельно.
Одна из важных характеристик областей применения – они позволяют нам должным образом высвобождать компоненты по истечении срока действия этих областей применения. Новая область применения создается с помощью метода
BeginLifetimeScope и высвобождает все соответствующие компоненты посредством вызова метода
Dispose
: using (var scope = container.BeginLifetimeScope())
{ var meal = scope.Resolve();
}
Строка 3: Уничтожение meal
Область применения создается из container посредством вызова метода
BeginLifetimeScope
. Возвращаемое значение реализует интерфейс
IDisposable
, поэтому можно поместить это значение в оператор using
. Поскольку возвращаемое значение и контейнер реализуют один и тот же интерфейс, scope можно использовать для разрешения компонентов таким же образом, как и в случае работы с самим контейнером.
После окончания работы с областью применения ее можно устранить. При использовании оператора using область применения автоматически устраняется при выходе из оператора. Но это также можно сделать и явным образом путем вызова метода
Dispose
При устранении scope мы также высвобождаем все компоненты, созданные с помощью области применения. В этом случае это означает, что вы высвобождаете диаграмму объектов meal
П римечание
Не забывайте, что высвобождение устранимого компонента и устранение компонента – это не одно и то же. Это сигнал контейнеру о том, что компонент может завершить свой срок действия. Если это T ransient -компонент или он ограничен областью применения, то он будет устранен. А если это Singleton компонент, то он будет продолжать действовать.
Ранее в этом разделе уже рассматривались способы конфигурирования компонентов в виде Singleton'ов или T ransient -компонентов. Конфигурирование компонента с целью получения границ этого экземпляра, ограниченных областью применения, выполняется аналогичным образом: builder.RegisterType()
.As()
.InstancePerLifetimeScope();

513
Подобно методам
SingleInstance и
InstancePerDependency метод
InstancePerLifetimeScope можно использовать для указания того, что жизненный цикл компонента должен соответствовать области применения, которая создала экземпляр этого компонента.
П одсказка
Autofac отслеживает большинство T ransient -компонентов, даже устраняемых, поэтому важно не забывать разрешать все компоненты области применения и устранять эту область после окончания ее использования.
Пока жизненный цикл компонента не закончен, Singleton'ы, в соответствии с их сущностью не высвобождаются. Однако если нам больше не нужен контейнер, мы можем высвобождать даже эти компоненты. Выполняется это посредством устранения самого контейнера: container.Dispose();
На практике это не столь важно, поскольку жизненный цикл контейнера находится в близких взаимоотношениях с жизненным циклом поддерживаемого им приложения.
Обычно мы сохраняем контейнер активным до тех пор, пока выполняется приложение, поэтому мы устраняем его только, когда приложение закрывается, при этом операционная система восстанавливает память.
Области применения позволяют нам обращаться ко множеству сценариев, в которых мы по обыкновению используем Web Request Cont ext или другой контекстный стиль существования. Это идиоматический способ реализации пользовательского жизненного цикла с помощью Autofac.
На этом наш обзор процесса управления жизненным циклом в рамках Autofac завершается. Компоненты можно конфигурировать с помощью сочетания областей применения экземпляров, и это справедливо даже при регистрации составных реализаций одной и той же абстракции. Мы еще не касались способов работы с составными компонентами, поэтому давайте направим все наше внимание в этом направлении.

514 13.3. Работа с составными компонентами
DI-контейнеры процветают благодаря их индивидуа льности, но их неопределенность порождает ряд трудностей. При использовании Constructor Injection единичный конструктор предпочтительнее перегружаемых конструкторов, поскольку в этом случае ясно, какой конструктор использовать в ситуации, когда у вас нет выбора. То же самое касается и преобразования абстракций к конкретным типам. Если мы пытаемся преобразовать конкретные составные типы к одной и той же абстракции, это приводит к неопределенности.
Несмотря на столь нежелательну ю особенность как неопределенность, нам часто приходится работать с составными реализациями единичного интерфейса. Это может происходить в следующих ситуациях:

Для разных потребителей должны использоваться разные специфичные типы

Зависимости являются последовательнос тями

Используются Decorator'ы
Мы рассмотрим каждую из этих ситуаций и увидим, как Autofac поочереди справляется с каждой из них. После прочтения раздела вы должны будете уметь регистрировать и разрешать компоненты даже тогда, когда в дело вступают составные реализации одной и той же абстракции.
Рассмотрим сначала способы предоставления более тщательного контроля, нежели тот, который предоставляет механизм автоматической интеграции.
В ыбор среди составных кандидатов
Автоматическая интеграция – удобный и мощный инструмент, но предоставляет нам меньшие возможности контроля. Пока все абстракции преобразуются в конкретные типы отдельно друг от друга, никаких трудностей не возникает, но как только мы вводим большее количество реализаций для одного и того же интерфейса, возникает неопределенность.
Давайте для начала повторим, как Autofac работает с составными регистрациями одной и той же абстракции.
Конфигурирова ние составных реализаций одного и того же сервиса
Как вы уже видели в разделе 13.1.2 "Конфигурирование Cont ainerBuilder", вы можете конфигурировать составные реализации одного и того же интерфейса: builder.RegisterType().As(); builder.RegisterType().As();
В этом примере классы
Steak и
SauceBéarnaise регистрируются в виде сервиса
IIngredient
. Выигрывает последняя регистрация, поэтому, если мы будем разрешать
IIngredient с помощью container.Resolve()
, то получим экземпляр
Steak

515
П одсказка
Выигрывает последняя регистрация данного сервиса. Она определяет экземпляры этого типа, используемые по умолчанию.
Можно попросить контейнер разрешить все компоненты
IIngredient
. Autofac обладает специально предназначеным для этих целей методом, но вместо того, чтобы его использовать Autofac полагается на типы взаим освязей. Тип взаимосвязи – это тип, обозначающий взаимосвязь, которую может интерпретировать контейнер. К примеру, для обозначения того, что нам нужны все сервисы, мы можем использовать
IEnumerable
: var ingredients = container.Resolve>();
Обратите внимание на то, что мы используем обычный метод
Resolve
, но запрашиваем
IEnumerable
. Autofac интерпретирует эту конструкцию как соглашение и отдает нам все компоненты
IIngredient
, которыми он обладает.
П одсказка
В противоположност ь
IEnumerable
мы можем также запросить массив. Результаты в обоих случаях будут одинаковы: мы получим все компоненты запрашиваемого типа.
В случае наличия регистраций, которые невозможно разрешить при запрашивании всех сервисов определенного типа, Autofac выдает исключение, поясняющее, что существуют зависимости, неудовлетвор яющие условиям. Т акое поведение соответствует разрешению единичного компонента, но отличается от поведения Castle W indsor или MEF.
При регистрации компонентов каждой регистрации можно присвоить свое собственное имя, которое в дальнейшем можно использовать для осуществления выбора между различными компонентами: builder.RegisterType()
.Named("meat"); builder.RegisterType()
.Named("sauce");
Как обычно мы начинаем с метода
RegisterType
, но вместо того, чтобы после него использовать метод
As
, мы используем метод
Named
, чтобы задать тип сервиса, а также его название. Это позволяет нам разрешать именованные сервисы путем передачи этого же имени в метод
ResolveNamed
: var meat = container.ResolveNamed("meat"); var sauce = container.ResolveNamed("sauce");
П римечание
Именованный компонент не считается компонентом по умолчанию. Если мы регистрируем только именованные компоненты, то не сможем разрешать экземпляр сервиса, используемый по умолчанию. Т ем не менее, ничто не мешает нам регистрировать компонент по умолчанию (неименованный) с помощью метода
As
, а сделать это можно в одном и том же операторе с помощью цепочки методов.

516
Присваивание имен компонентам с помощью строк – довольно универсальная возможность DI-контейнеров, но Autofac также позволяет идентифициро вать компоненты с помощью произвольных ключей: var meatKey = new object(); builder.RegisterType().Keyed(meatKey);
В роли ключа может выступать любой объект, который впоследствии можно использовать для разрешения компонента: var meat = container.ResolveKeyed(meatKey);
Подразумевая необходимость, всегда разрешать сервисы в одном Composition Root, мы, скорее всего, не должны ждать появления такой неопределенности на этом уровне.
П одсказка
Если вы обнаружите, что вызываете метод
Resolve с конкретным именем или ключом, подумайте над тем, сможете ли вы сменить свой подход на менее неопределенный.
Т ем не менее, именованные экземпляры или экземпляр ы, идентифицируе мые с помощью ключа, можно использовать для осуществления выбора среди нескольких экземпляров при конфигурировании зависимостей данного сервиса.
Ре гистрация именованны х зависимостей
Иногда бывает необходимо переопределить обычное поведение для того, чтобы обеспечить более разветвленный контроль над тем, куда какая зависимость отправляется.
Кроме того, возможны ситуации, при которых приходится сталкиваться с неопределенным API. В качестве примера рассмотрим следующий конструктор: public ThreeCourseMeal(ICourse entrée,
ICourse mainCourse, ICourse dessert)
В этом примере присутствуют три одинаковым образом типизированных зависимости, каждая из которых является отдельной сущностью, не похожей на другие. В большинстве случаев необходимо преобразовывать каждую из этих зависимостей в отдельный тип. В следующем листинге продемонстрированы способы регистрации преобразований
ICourse
Листинг 13-2: Регистрация именованных course builder.RegisterType()
.Named("entrée"); builder.RegisterType()
.Named("mainCourse"); builder.RegisterType()
.Named("dessert");
В этом примере вы регистрируете три именованных компонента, преобразуя
Rilettes в экземпляр под названием "ent rée",
CordonBleu
– в экземпляр с именем "mainCourse", а
MousseAuChocolat
– в экземпляр под названием "dessert".

517
При такой конфигурации вы теперь можете регистрировать класс
ThreeCourseMeal с помощью именованных регистраций. Оказывается, это довольно сложно. В следующем листинге я сначала продемонстрирую вам, на что это похоже, а затем мы подробно разберем этот пример, чтобы понять, что происходит.
Листинг 13-3: Переопределение автоматической интеграции
1.
builder.RegisterType()
2.
.As()
3.
.WithParameter(
4.
(p, c) => p.Name == "entrée",
5.
(p, c) =>
6.
c.ResolveNamed("entrée"))
7.
.WithParameter(
8.
(p, c) => p.Name == "mainCourse",
9.
(p, c) =>
10.
c.ResolveNamed("mainCourse"))
11.
.WithParameter(
12.
(p, c) => p.Name == "dessert",
13.
(p, c) =>
14.
c.ResolveNamed("dessert"));
Строка 3: Определение параметра
Строка 4: Фильтры
Строка 5-6: Определение значений
Метод
WithParameter позволяет предоставлять значения параметров для конструктора
ThreeCourseMeal
. Одна из этих перегрузок принимает в качестве входных параметров два аргумента. Первый аргумент – предикат, который определяет, является ли этот параметр результатом этого конкретного вызова метода. Для первого параметра вы задаете условие, что он касается только параметра под названием entree
. Если это выражение имеет значение true
, то выполняется второй блок кода, определяющий значение для параметра entree
. Параметр c
– это экземпляр
IComponentContext
, который можно использовать для разрешения именованного компонента entree
П одсказка
Аргументы метода
WithParameter
– это разновидность паттерна Tester-Doer.
Рассмотрим подробнее то, что происходит. Метод
WithParameter действительно является оберткой класса
ResolvedParameter
, который обладает следующим конструктором: public ResolvedParameter(
Func predicate,
Func valueAccessor);
Параметр predicate
– это тест, который определяет, будет ли вызываться делегат valueAccessor
: если параметр predicate возвращает true
, то для определения значения параметра вызывается valueAccessor
. Оба делегата принимают в качестве входных данных одну и ту же информацию: информацию о параметре в виде объекта
ParameterInfo и
IComponentContext
, который можно использовать для разрешения других компонентов. Когда Autofac использует экземпляр ы
ResolvedParameter
, то при вызове делегатов он возвращает оба эти значения. Иногда нет другого варианта, кроме как

518 старательно использовать метод
WithParameter для каждого параметра конструктора. Но в остальных случаях можно воспользоваться преимуществами соглашений.
Разрешение именованны х компонентов с помощью соглашения
Если вы внимательно проанализировали листинг 13-3, то возможно обратили внимание на повторяющийся паттерн. Каждый вызов метода
WithParameter относится только к одному параметру конструктора, но каждый valueAccessor выполняет то же самое: он использует
IComponentContext для разрешения компонента
ICourse
, имеющего то же название, что и параметр.
Нет никакого требования, согласно которому мы обязаны были бы называть компонент в честь параметра конструктора. Но всякий раз, когда мы так поступаем, мы можем воспользоваться преимуществами этого соглашения и переписать листинг 13-3 более простым образом. В следующем листинге продемонстрировано, как это сделать.
Листинг 13-4: Переопределение автоматической интеграции с помощью соглашения builder.RegisterType()
.As()
.WithParameter(
(p, c) => true,
(p, c) => c.ResolveNamed(p.Name, p.ParameterType));
Возможно, это покажется удивительным, но обратиться ко всем трем параметрам конструктора класса
ThreeCourseMeal можно с помощью одного и того же вызова метода
WithParameter
. Осуществить это можно, сформулировав соглашение о том, что этот экземпляр будет управлять всяким параметром, какой Autofac смог бы ему передать.
Поскольку вы используете этот метод только для конфигурирования класса
ThreeCourseMeal
, соглашение применяется только в рамках этой ограниченной области.
Поскольку предикат всегда возвращает значение true
, второй блок кода будет вызываться для всех трех параметров конструктора. Во всех трех случаях этот блок кода будет просить
IComponentContext разрешить компонент, имеющий те же самые имя и тип, что и параметр. Функционально это аналогично тому, что вы делали в листинге 13-3.
П редупреждение
Идентифицировать параметры по их именам удобно, но это не безопасно для рефакторинга. Если вы переименуете параметр, то можете разрушить конфигурацию (это зависит от используемого вами инструмента рефакторинга).
Переопределение автоматической интеграции посредством явного преобразования параметров в именованные компоненты – это повсеместно используемое решение.
Сделать это можно даже в тех случаях, когда именованные компоненты конфигурируются в одном модуле, а потребитель находится совсем в другом модуле. Это возможно, поскольку единственной идентификацией, связывающей именованный компонент с параметром, является имя.
Т акой подход можно использовать всегда, но, если нам придется управлять большим количеством имен, то это будет довольно хрупким решением. Когда первоначальной причиной, побуждающей нас использовать именованные компоненты, является борьба с неопределенностью, наилучшим решением этой проблемы станет создание своего

519 собственного API, которое позволит избавиться от этой неопределенности. Все это приводит к улучшению всего дизайна приложения.
В следующем разделе мы рассмотрим возможность использования менее неопределенного и более гибкого подхода, при котором обед может состоять из нескольких блюд. Перед этим нам необходимо изучить то, как Autofac работает со списками и последовательност ями.
Интеграция последовательностей
В разделе 10.3.2 "Разработка пользовательского стиля существования" мы обсуждали, как выполнить рефакторинг явного класса
ThreeCourseMeal к более универсальному классу
Meal
, который обладает приведенным ниже конструктором: public Meal(IEnumerable courses)
В этом разделе мы рассмотрим то, как можно сконфигурировать Autofac, чтобы он интегрировал экземпляры
Meal с соответствующими зависимостями
ICourse
. После рассмотрения этого вопроса вы должны будете приобрести хорошее понимание тех возможностей, которые доступны в случае необходимости конфигурирования экземпляров, имеющих последовательности зависимостей.
Автоматическая инте грация последовательностей
Autofac довольно хорошо разбирается в последовательностях, поэтому, если нам необходимо использовать все зарегистрированные компоненты этого сервиса, то нам нужен как раз механизм автоматической интеграции. Например, имея сконфигурированные в листинге 13-2 экземпляр ы
ICourse
, можно сконфигурировать сервис
IMeal следующим образом: builder.RegisterType().As();
Обратите внимание на то, что это совершенно стандартное преобразование конкретного типа в абстракцию. Autofac будет автоматически понимать конструктор
Meal и определять, что правильным направлением действия является разрешение всех компонентов
ICourse
. При разрешении
IMeal вы получите экземпляр
Meal
, компонентами которого являются
ICourse из листинга 13-2:
Rillettes
,
CordonBleu и
MousseAuChocolat
Autofac автоматически управляет последовательностями и, пока мы не укажем обратное, выполняет то, чего мы от него и ждем: разрешает последовательность зависимостей всех зарегистрированных компонентов этого типа. Т олько если нам нужно отобрать лишь некоторые компоненты из большого набора, нам приходится выполнять больше действий.
Рассмотрим то, как это можно сделать.
О тбор нескольких компонентов из большого набора
Используемая Autofac по умолчанию стратегия внедрения всех компонентов зачастую является правильной линией поведения. Но, как показывает рисунок 13-6, возможны случаи, когда нам нужно отобрать только несколько зарегистрированных компонентов из большого набора всех зарегистрированных компонентов.

520
Рисунок 13-6: В ситуации, продемонстрированной слева, мы хотим явным образом отобрать определенные зависимости из большого списка всех зарегистрированных компонентов. Это отличается от ситуации, приведенной справа, когда мы отбираем все без разбора.
Когда мы ранее позволяли Autofac автоматически интегрировать все сконфигурированные экземпляр ы, это соответствовало ситуации, изображенной в правой части рисунка 13-6.
Если нам нужно зарегистрировать компонент так, как это показано в левой части рисунка, то мы должны явно определить, какие компоненты необходимо использовать.
Для получения такого результата можно еще раз применить метод
WithParameter так, как вы уже использовали его в листингах 13-3 и 13-4. Все это время вы работали с конструктором
Meal
, который в качестве входной информации принимал только один- единственный параметр. В следующем листинге продемонстрировано, как можно реализовать составляющу ю метода
WithParameter
, которая предоставляет значение параметра, так, чтобы явным образом отбирать именованные компоненты из
IComponentContext
Листинг 13-5: Внедрение именованных компонентов в последовательность builder.RegisterType()
.As()
.WithParameter(
(p, c) => true,
(p, c) => new[]
{ c.ResolveNamed("entrée"), c.ResolveNamed("mainCourse"), c.ResolveNamed("dessert")
});

521
Как вы уже видели в разделе 13.3.1 "Выбор среди составных кандидатов", метод
WithParameter в качестве входных параметров принимает два делегата. Первый – это предикат, который используется для того, чтобы определить, должен ли вызываться второй делегат. В этом примере мне захотелось полениться, и я вернул значение true
. Вы знаете, что у конструктора класса
Meal есть только один параметр, поэтому метод
WithParameter будет работать. Т ем не менее, если вы впоследствии измените класс
Meal таким образом, что у его конструктора будет два параметра, метод
WithParameter уже не будет работать корректно. Поэтому безопаснее всего будет установить явную проверку имени параметра.
Второй делегат предоставляет значение для параметра. Для разрешения трех именованных компонентов в массив вы используете
IComponentContext
. В результате получаем массив
ICourse
, который сравним с
IEnumerable
Autofac понимает последовательности. До тех пор пока не появляется необходимость отбирать только некоторые компоненты из всех сервисов указанного типа, Autofac автоматически все делает правильно. Автоматическая интеграция применяется не только для единичных экземпляров, но также и для последовательнос тей. А контейнер преобразует последовательнос ть во все сконфигурированные экземпляр ы соответствующего типа.
Потребители, которые полагаются на последовательности зависимостей, могут быть самыми интуитивно понятными пользователю составными экземплярами одной и той же абстракции. Но перед тем как мы полностью отойдем от данной темы, нам необходимо рассмотреть последний (и, возможно, слегка неожиданный) случай, когда в дело вступают составные экземпляры.
Интеграция Decorator'ов
В разделе 9.1.2 "Паттерны и принципы механизма перехвата" мы обсуждали то, насколько паттерн проектирования Decorator полезен при реализации сквозных сущностей. По определению Decorator'ы представляют собой составные типы одной и той же абстракции.
У нас есть, по крайней мере, две реализации абстракции: сам Decorat or и вложенныйй тип.
Если бы мы помещали Decorat or'ы в стек, то у нас было бы еще больше реализаций.
Это еще один пример составных регистраций одного и того же сервиса. В отличие от предыдущих разделов эти регистрации не являются концептуально равносильными, а зависят друг от друга. В этом разделе я рассмотрю два разных способа конфигурирования
Autofac для работы с данным паттерном.
С оздание обертки с помощью метода WithParameter
Метод
WithParameter предлагает универсальный способ определения того, как создаются и внедряются компоненты. В разделах 13.3.1 "Выбор среди составных кандидатов" и
13.3.2 "Интеграция последовательност ей" вы уже видели, как можно использовать метод
WithParameter для отбора конкретных компонентов для параметров конструктора. Кроме того, метод
WithParameter
– это отличный способ предоставления параметров для
Decorator'ов.

522
Рассмотрим способы использования метода
WithParameter для конфигурирования класса
Breading
, который является оберткой
IIngredient
. Для получения экземпляра, оберткой которого должен стать
WithParameter
, он использует паттерн Constructor Injection: public Breading(IIngredient ingredient)
Чтобы получить
Cotoletta
, вам следует обернуть
VealCutlet
(еще один
IIngredient
) в класс
Breading
. То есть вы собираетесь внедрить
VealCutlet в
Breading
. В следующем листинге продемонстрировано, как для этих целей можно использовать метод
WithParameter
Листинг 13-6: Создание обертки с помощью метода
WithParameter builder.RegisterType().Named("cutlet"); builder.RegisterType()
.As()
.WithParameter(
(p, c) => p.ParameterType == typeof(IIngredient),
(p, c) => c.ResolveNamed("cutlet"));
Breading
– это Decorator, но вам же нужно что-то обертывать, поэтому вы регистрируете
VealCutlet в виде именованного компонента. В этом примере вы регистрируете
VealCutlet перед
Breading
, но можно сделать это и по-другому. Порядок регистраций не имеет значения.
При регистрации
Breading для определения параметра ingredient конструктора класса
Breading вы используете метод
WithParameter
. Вы реализуете предикат, проверяя, что тип параметра –
IIngredient
, и предоставляете значение для параметра путем разрешения именованного компонента cutlet из заданного
IComponentContext
В этом примере вы использовали именованную регистрацию
IIngredient для регистрации компонента
VealCutlet
. Это делает компонент
Breading компонентом
IIngredient по умолчанию. Еще один вариант – регистрировать
VealCutlet и как
IIngredient
, и как
VealCutlet
. В следующем примере продемонстрирован этот подход в сочетании со строго типизированным делегатом.
С оздание обертки с помощью де легатов
Вместо того чтобы обращаться к параметрам конструктора по типу или имени, мы можем написать строго типизированный блок кода, в котором используется конструктор: builder.RegisterType()
.As(); builder.Register(c => new Breading(c.Resolve()))
.As();
В качестве альтернативы регистрации
VealCutlet в виде именованного компонента можно также регистрировать его и как
IIngredient
, и как
VealCutlet
. При таком подходе важно делать это до регистрации
Breading
, псокольку в противном случае
VealCutlet станет компонентом
IIngredient по умолчанию.
Вместо метода
RegisterType
, который вы в основном использовали до настоящего момента, можно также зарегистрировать сервис и с помощью метода под названием
Register
. Существует две перегрузки этого метода, и каждая из них принимает в качестве

523 входного параметра делегат, который создает рассматриваемый сервис. Чтобы зарегистрировать сервис
IIngredient
, вы реализуете блок кода, который создает новый экземпляр
Breading путем прямого вызова конструктора. Чтобы передать значение в параметр конструктора ingredient
, вы разрешаете тип
VealCutlet из переданного
IComponentContext
. Это возможно, поскольку вы зарегистрировали
VealCutlet как конкретный тип, а также как
IIngredient
П римечание
Вы также могли бы разрешить
VealCutlet по имени, если бы зарегистрировали его в виде именованного компонента, как делали это в предыдущем примере.
Если вы попросите контейнер разрешить
IIngredient
, он передаст
IComponentContext в качестве входного параметра в блок кода, который вы определили в методе
Register
. При выполнении блока кода из контекста разрешается экземпляр
VealCutlet и передается в конструктор
Breading
, который возвращает экземпляр
Breading
Преимущество такого подхода заключается в том, что в блоке кода вы пишете код, в котором используется конструктор
Breading
. Это обычная строка кода, поэтому она проверяется компилятором. Это придает вам уверенности в том, что если метод
Register компилируется, значит, обертка для
VealCutlet будет создаваться корректно.
Несмотря на то, что строгая типизированност ь более безопасна, ее еще и сложно сопровождать. Если вы впоследствии решите добавить в конструктор
Breading еще один параметр, то блок кода больше не будет компилироват ься, и вам придется вручную решить эту проблему. Это не было бы столь необходимо, если бы вы использовали метод
WithParameter
, поскольку Autofac смог бы разобрать новый параметр с помощью механизма автоматической интеграции.
Как вы видели в этом разделе, существуют различные способы конфигурирования
Decorator'ов. Строго типизированный подход более безопасен, но может потребовать больше затрат на сопровождение. Более слабо типизированное API является более гибким решением и позволяет Autofac справляться с изменениями вашего API, но ценой меньшей типовой безопасности.
П римечание
В этом разделе мы не обсуждали механизм перехвата во время выполнения. Несмотря на то, что Autofac имеет
Seam
'ы, которые позволяют использовать механизм перехвата, он также обладает встроенной поддержкой динамически создаваемых прокси. Эти
Seam
'ы можно применять, чтобы использовать для создания таких классов другие библиотеки
(например, Castle Dynamic Proxy). Но поскольку они не являются частью Autofac, эта тема выходит за рамки этой главы.
Autofac позволяет нам работать с составными экземплярами разными способами. Мы можем регистрировать компоненты в виде альтернатив друг другу, в виде пиров, которые разрешаются в виде последовательностей, или в виде иерархических Decorator'ов. В большинстве случаев Autofac поймет, что надо делать, но мы всегда можем задать, как компонуются сервисы, если нам нужен более явный контроль.

524
Кроме того, это может происходить в ситуации, когда нам нужно иметь дело с API, отклоняющимися от Constructor Injection. До настоящего момента вы наблюдали за тем, как регистрировать компоненты, включая то, как определять области применения, и как работать с составными компонентами. Но до этого момента мы позволяли контейнеру подключать зависимости, явным образом предполагая, что все компоненты используют
Constructor Injection. Поскольку это не всегда происходит именно так, в следующем разделе мы сделаем краткий обзор того, как работать с классами, экземпляры которых должны создаваться особым образом.

525 13.4. Регистрация сложных API
До настоящего момента мы рассматривали то, как можно конфигурировать компоненты, использующие Constructor Injection. Одним из главных преимуществ Constructor Injection является то, что DI-контейнеры, например, Autofac, могут с легкостью понимать, как компоновать и создавать все классы диаграммы зависимостей.
Все становится менее понятным, когда API не столь хорошо функционируют. В данном разделе вы увидите, как работать с простейшими аргументами конструктора, статическими фабриками и Property Injection. Все это требует особого внимания. Начнем с рассмотрения классов, конструкторы которых принимают в качестве параметров простейшие типы, например, строки и целые числа.
Конфигурирование простейших зависимостей
Пока мы внедряем абстракции в потребителей, все в порядке. Но этот процесс усложняется, если конструктор зависит от простейшего типа, например, строкового, числового или перечисляемого. Наиболее часто это случается в реализациях доступа к данным, которые принимают в качестве параметра конструктора строку соединения. Но в то же время это является более общей проблемой, касающейся всех строковых и числовых типов.
В сущности, регистрация строкового или числового типа в качестве компонента контейнера не имеет особого смысла. Но в рамках Autofac это, по крайней мере, осуществимо.
Рассмотрим в качестве примера приведенный ниже конструктор: public ChiliConCarne(Spiciness spiciness)
В этом примере
Spiciness имеет перечисляемый тип: public enum Spiciness
{
Mild = 0,
Medium,
Hot
}
П редупреждение
Согласно эмпирическому правилу перечисления являются code smell'ами и их нужно преобразовывать в полиморфные классы (имеющие разное состояние). Тем не менее, для данного примера они вполне нам подходят.
Если вы хотите, чтобы все потребители
Spiciness использовали одно и то же значение, можно зарегистрировать
Spiciness и
ChiliConCarne независимо друг от друга: builder.Register(c => Spiciness.Medium); builder.RegisterType().As();

526
Когда вы впоследствии будете разрешать
ChiliConCarne
, его
Spiciness будет иметь значение
Medium
, как и все остальные компоненты, зависимые от
Spiciness
Если вы будете достаточным образом контролировать взаимосвязь
Spiciness и
ChiliConCarne
, вы сможете использовать метод
WithParameter таким же образом, как и в листингах 13-4, 13-5, 13-6: builder.RegisterType()
.As()
.WithParameter("spiciness", Spiciness.Hot);
Поскольку вы собираетесь передать в параметр spiciness конкретное значение, вы можете воспользоваться другой перегрузкой метода
WithParameter
, которая в качестве входных данных принимает имя и значение параметра. Эта перегрузка делегирует полномочия другому
WithParameter путем создания экземпляра
NamedParameter из имени и значения параметра.
NamedParameter также наследуется от
Parameter
, как и
ResolvedParameter
Оба описанных здесь варианта стимулируют автоматическую интеграцию на предоставление конкретного значения для компонента. Как уже обсуждалось в разделе
13.3 "Работа с составными компонентами", такой подход имеет как свои преимущества, так и недостатки. Если вам нужна строго типизированная конфигурация, которая вызывает конструктор или статическую фабрику, вы также сможете это сделать.
Регистрация объектов с помощью блоков кода
Еще один вариант создания компонента с примитивным значением – использовать метод
Register
, позволяющий передавать делегат, который создает компонент: builder.Register(c => new ChiliConCarne(Spiciness.Hot));
Вы уже видели метод
Register
, когда мы обсуждали Decorator'ы в разделе 13.3.3
"Интеграция Decorator'ов". Всякий раз при разрешении компонента
ICourse будет вызываться конструктор
ChiliConCarne с параметром
Spiciness.Hot
П римечание
Метод
Register имеет безопасный тип, но не позволяет использовать автоматическую интеграцию.
Когда дело касается класса
ChiliConCarne
, вам предоставляется выбор между автоматической интеграцией и использованием блока кода. Но другие классы более ограничены: их экземпляры нельзя создать с помощью открытого конструктора. Чтобы создать экземпляры типа, вместо открытого конструктора вам приходится использовать некоторого рода фабрику. Для DI-контейнеров это всегда проблематично, поскольку по умолчанию им нужны открытые конструкторы.
Рассмотрим приведенный ниже пример конструктора открытого класса
JunkFood
: internal JunkFood(string name)

527
Даже если класс
JunkFood является открытым, конструктор расположен внутри него.
Очевидно, экземпляры
JunkFood должны создаваться с помощью статического класса
JunkFoodFactory
: public static class JunkFoodFactory
{ public static IMeal Create(string name)
{ return new JunkFood(name);
}
}
С точки зрения Autofac, это проблемное API, поскольку в нем отсутствуют точно выраженные и заданные соглашения касательно статических фабрик. Т ут требуется помощь – и мы можем предоставить ее посредством блока кода, который Autofac может исполнять для того, чтобы создать экземпляр: builder.Register(c =>
JunkFoodFactory.Create("chicken meal"));
В этот раз вы используете метод
Register для создания компонента, вызывая статическую фабрику в рамках блока кода. Всякий раз при разрешении
IMeal будет вызываться
JunkFoodFactory.Create и возвращаться результат.
Является ли написание блока кода для создания экземпляра лучшим вариантом, нежели прямой вызов кода? При использовании блока кода внутри вызова метода
Register мы приобретаем следующие преимущества:

IMeal преобразуется в
JunkFood

Область применения экземпляра остается доступной для конфигурирования.
Несмотря на то, что для создания экземпляра вызывается блок кода, он может и не вызываться всякий раз при запросе экземпляра. По умолчанию он вызывается, но если мы изменим область применения экземпляра на Singleton, то блок кода будет вызываться только один раз, а результат будет кэшироваться и впоследствии повторно использоваться.
Последним рассматриваемым нами отклонением от Constructor Injection является Propert y
Injection.
Интегрирование с помощью Property Injection
Property Injection – это менее определенная форма механизма внедрения зависимостей, поскольку компилятор не принудает нас задавать значение свойства, доступного для записи. Это касается и Autofac, который будет оставлять доступные для записи свойства незаполненными до тех пор, пока мы явно не попросим его заполнить их.
Рассмотрим класс
CaesarSalad
: public class CaesarSalad : ICourse
{ public IIngredient Extra { get; set; }
}

528
По всеобщему заблуждению в состав салата "Цезарь" входит курица, но это не правда. По существу "Цезарь" является салатом, но, поскольку с курицей он вкуснее, то ее часто предлагают использовать в нем в качестве дополнительного ингредиента. Класс
CaesarSalad моделирует такую возможность посредством доступного для записи свойства под названием
Extra
Если вы конфигурируете только класс
CaesarSalad
, явно не обращаясь к свойству
Extra
, то этому свойству не будет присвоено значение. Вы все равно можете разрешать экземпляр, но свойство
Extra будет иметь значение по умолчанию, которое ему присвоил конструктор (если только это имеет место).
Существует несколько способов, с помощью которых можно сконфигурировать
CaesarSalad таким образом, чтобы свойство
Extra заполнялос ь соответствующим образом. Самым простым способом является использование метода
PropertiesAutowired
: builder.RegisterType()
.As()
.PropertiesAutowired(); builder.RegisterType().As();
Поскольку метод
PropertiesAutowired является частью простого API регистрации, вы можете вызвать его для того, чтобы сообщить Autofac, что ему необходимо автоматически интегрировать доступные для записи свойства класса
CaesarSalad
. Autofac будет автоматически интегрировать только те свойства, о способе заполнения которых он осведомлен, поэтому вы также регистрируете
Chicken в виде
IIngredient
. Если бы вы это не сделали, то свойство
Extra было бы проигнорировано.
Когда вы на основании этой регистрации разрешаете
ICourse
, вы получаете экземпляр
CaesarSalad
, свойству
Extra которого присвоен экземпляр
Chicken
Если вам нужен более подробный контроль, нежели тот, который предоставляется посредством метода
PropertiesAutowired
, то вы можете воспользоваться методом
WithProperty
, который похож на метод
WithParameter
, используемый вами ранее: builder.RegisterType().As(); builder.RegisterType()
.As(
.WithProperty(new ResolvedParameter(
(p, c) => p.Member.Name == "set_Extra",
(p, c) => c.Resolve()));
Метод
WithProperty отражает уже полюбившийся вам метод
WithParameter
: он принимает в качестве входных данных только один аргумент
Parameter и также обладает перегрузкой, которая в качестве параметров принимает имя и значение свойства.
Чтобы должным образом разрешить метод
Extra
, можно воспользоваться доверенным классом
ResolvedParameter
. Что касается свойств, переданный нами предикат имеет небольшую отличительную черту, поскольку Autofac вызывает блок кода с аргументом
ParameterInfo
, а не с
PropertyInfo
. Параметр p
олицетворяет параметр value
, который всегда потенциально доступен при реализации свойства, поэтому нам необходимо перейти к
Member
, который определяет этот параметр.
Member
– это экземпляр
MethodInfo
,

529 поэтому нам нужно ознакомиться с тем, как реализуются C# свойства на уровне интерфейса: в действительности свойства
Extra
– это метод под названием set_Extra
Когда предикат передан, легко реализовать получение значения путем разрешения
IIngredient из переданного
IComponentContext
Использование метода
WithProperty дает нам более разветвленный уровень контроля над
Property Injection, при этом мы сохраняем слабое связывание. Если нам нужен другой строго типизированный подход, то это тоже возможно.
Autofac позволяет нам использовать блоки кода, которые будут вызываться в течение жизненного цикла компонента при возникновении определенного события. Мы можем перехватывать эти события, чтобы заполнять свойства по мере того, как создается компонент.
Одно из таких событий – событие
OnActivating
, которое Autofac вызывает всякий раз при создании нового экземпляра компонента. Это событие можно использовать для заполнения свойства
Extra
, пока Autofac не вернет экземпляр
CaesarSalad
: builder.RegisterType().As(); builder.RegisterType()
.As()
.OnActivating(e => e.Instance.Extra = e.Context.Resolve());
Метод
OnActivating дает вам возможность выполнить какое-нибудь действие над компонентом, пока Autofac не вернет его тому объекту, который его запрашивает. В качестве единственного параметра он принимает
Action>
, который можно использовать для реализации выбранной вами логики постобработки. Параметр e олицетворяет аргументы события, а также обладает свойством
Instance типа
CaesarSalad и свойством
Context
, которое можно использовать для разрешения других компонентов. Это сочетание вы используете для того, чтобы разрешить
IIngredient и вернуть результат в свойство
Extra
. При разрешении
ICourse вы получите экземпляр
CaesarSalad
, свойство
Extra которого имеет значение
Chicken
Поскольку свойство
Instance привязано к аргументу интерфейса
IActivatingEventArgs
, имеющему generic-тип, этот подход является строго типизированным и влечет за собой свои преимущества и недостатки.
В этом разделе вы увидели, как можно использовать Autofac для работы с более трудными
API разработки. Для интеграции конструкторов и свойств с сервисами можно использовать различные отклонения от абстрактного класса
Parameter
, чтобы при этом соблюсти сходство с автоматической интеграцией, или можно воспользоваться методом
Register и блоком кода, чтобы соблюсти большую типовую безопасность.
В целом Propert y Injection отлично поддерживается и механизмом автоматической интеграции и строго типизированными присваиваниями.

530 13.5. Резюме
В этой главе вы опробовали в действии DI-контейнер Autofac. Несмотря на то, что Autofac
– это контейнер второго поколения, он все равно достаточно комплексный и подходит для большинства каверзных ситуаций, с которыми мы сталкиваемся при использовании DI- контейнеров. Архитектура этого контейнера напрямую построена на особенностях .NET
3.5 и C# 3.0, но, хотя внутри Autofac использует делегаты и блоки кода, общее API просто в использовании.
Важным вопросом, касающимся Autofac, является недвусмысленность. Он не пытается предугадать, что мы имеем ввиду, а предлагает легкое в использовании API, которое предоставляет нам возможность явным образом разрешать различные возможности.
Одним из примеров такой недвусмысленности является то, что в отличие от других DI- контейнеров Autofac навязывает более строгую концепцию разделения между процессом конфигурирования и потребления контейнера. Компоненты мы конфигурируем с помощью экземпляра
ContainerBuilder
, но
ContainerBuilder не умеет разрешать компоненты. После окончания процесса конфигурирования
ContainerBuilder
, мы используем его для создания
IContainer
, который можно использовать для разрешения компонентов.
Мы можем конфигурировать
ContainerBuilder любым возможным способом: с помощью императивного кода, посредством XML или путем задания соглашений. А конфигурации мы можем пакетировать в модули.
Т о, как Autofac использует механизм управления жизненным циклом, несколько отличается от того, как его используют другие DI-контейнеры. Т акие стандартные стили существования, как Transient и Singleton являются встроенными. Но другие контекстные стили существования, например, Web Request Cont ext , необходимы для областей применения, в которых мы явным образом взаимодейству ем с контейнером с целью определить контекст, в рамках которого разрешаются и высвобождаются компоненты.
Это в полной мере касается стиля Web Request Context . С другой стороны, эта модель не позволяет нам реализовывать пользовательский стиль существования Pooled или кэшированный стиль существования. Т аким образом, простого способа реализации пользовательских стилей существования с помощью Autofac нет. По мнению Николаса
Блумхардта (создателя Autofac), такие ситуации никогда не встречались на дискуссионных форумах, поэтому, вероятнее всего, это не столь большая проблема. Чаще всего предлагаемого Autofac механизма управления жизненным циклом бывает более чем достаточно.
Autofac – современный DI-контейнер, который предлагает достаточно исчерпывающий набор возможностей. В следующей главе мы рассмотрим еще один DI-контейнер второго поколения – Unity.

531 14. Unity
Меню:

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

Управление жизненным циклом

Работа с составными компонентами

Конфигурирование сложных API
В предыдущей главе мы рассматривали контейнер Autofac – один из недавно появившихся DI-контейнеров. Еще один современный DI-контейнер – это Unity. Его мы и рассмотрим в этой главе.
Autofac можно назвать DI-контейнером второго поколения , поскольку он зародился и развивался прямиком на .NET 3.5, и не был нагружен никакими чертами более ранних версий .NET . Несмотря на то, что Unity появился примерно в то же самое время, он унаследовал более консервативный подход. Unity 1.0 был выпущен в мае 2008 года, но создавался он под .NET 2.0, поскольку его разработчики осознавали, что большинству организаций потребуется некоторое время на то, чтобы перейти на версию .NET 3.5.
Unity – это модуль приложений, разработанный группой Patterns&Practices компании
Microsoft. Но не обращайте внимание на это название: модуль приложений – это просто повторно используемая библиотека с соответствующей документацией и примерами.
Unity и Enterprise Library
Некоторые путают Unit y и Ent erprise Library (еще одно решение группы
Patterns& Practices) или, по крайней мере, то, как они взаимосвязаны друг с другом.
Давайте внесем некоторую ясность.
Unity – это самостоятельная библиотека. Ему не нужна Enterprise Library.
С другой стороны, в состав Enterprise Library входит Unity, хотя и выступает он в этом случае всего лишь в роли контейнера по умолчанию для Enterprise Library. При необходимости Unit y в Ent erprise Library может быть заменен другим DI-контейнером.
Согласно некоторому совершенно не научному интернет-исследованию, Unity является самым широко используемым DI-контейнером. Это довольно удивительно, но, скорее всего, связано с тем фактом, что разработала его компания Microsoft . Существующие конкуренты, к примеру, Castle W indsor или StructureMap, не были отмечены в этом исследовании, поэтому, вероятнее всего, Unity познакомил с понятием DI-контейнеров совершенно новый сегмент пользователей, которые ранее ничего о нем не знали.
В этой главе мы в такой же последовательно сти, как делали это и для других DI- контейнеров, рассмотрим контейнер Unit y. Вы увидите, как можно использовать Unity для применения принципов и паттернов, приведенных ранее в частях 1-3. На рисунке 14-1 представлена структура главы.
Рисунок 14-1: Эта глава состоит из четырех разделов. Первый раздел знакомит нас с
Unity и демонстрирует способы конфигурирования и разрешения компонентов.
Следующие три раздела связаны с паттернами применения, которые требуют

532 дополнительного внимания. Их можно прочитать по порядку или же пропустить некоторые из этих разделов и прочитать только те, которые вас интересуют.
Первый раздел знакомит нас с API контейнера Unity и должен рассматриваться в качестве предпосылки следующих трех разделов. Каждый из четырех разделов может быть прочитан независимо от остальных разделов, несмотря на то, что в четвертом разделе используются некоторые классы, которые вводятся в третьем разделе. Эти классы имеют относительно ясные имена, поэтому четвертый раздел можно прочитать, не читая перед этим третий. Но, с другой стороны, у вас случайно может появиться необходимость вернуться к этому разделу.
Подобно Castle W indsor контейнер Unity поддерживает несколько дополнительных возможностей, например, пользовательские жизненные циклы и механизм перехвата. В этой главе приводятся примеры и того, и другого явления, а также множество других более универсальных возможностей DI-контейнера. Все эти возможности суммированы, именно поэтому эта глава является одной из самых длинных глав этой книги.
Эта глава должна дать вам возможность приступить к работе, а также справиться с большинством общепринятых проблем, которые могут возникнуть при ежедневном использовании Unity. Это не окончательная трактовка Unity, а более подробную информацию о Unity можно получить онлайн. Возможно, из доступных на сегодняшний момент DI-контейнеров Unity лучше всего задокументирован.
Вы можете прочитать эту главу, не читая при этом остальные главы части 4, специально для того, чтобы познакомиться с Unity, или можете прочитать ее совместно с остальными главами части 4, чтобы сравнить DI-контейнеры. Цель данной главы – показать, как Unity связан с паттернами и принципами, описанными в остальной части книги, и как он их реализует.
14.1. Знакомство с Unity
14.2. Управление жизненным циклом
14.3. Работа с составными компонентами
14.4. Конфигурирование сложных API
14.5. Резюме

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

1   ...   31   32   33   34   35   36   37   38   ...   43


написать администратору сайта