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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница27 из 43
1   ...   23   24   25   26   27   28   29   30   ...   43
section on your web.config. If
you’re running IIS7 in Integrated Mode you will need to add it to section under

(Похоже, вы забыли зарегист рироват ь HTTP-модуль
Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule
Добавьт е ‘
type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Wind so r" />’ в
раздел ваш его файла web.config. Если IIS7 запущен в инт егрированном
режиме, вам нужно будет добавить эт от код в раздел после
)
Поскольку выдаются сообщения об ошибках, приведенное выше исключение является показательным. Оно точно указывает, что вам необходимо сделать.
В любом случае, важно отметить, что стиль существования PerW ebRequest использует
HTTP-модуль для того, чтобы указать, какой веб-запрос на данный момент выполняется.
Одно из последствий этого – нам необходимо зарегистрировать HT T P-модуль так, как я это описывал выше, другое – данный конкретный стиль существования работает т олько в

385 рамках веб-запросов. Если мы попытаемся использовать его в других типах приложений, мы получим то же самое исключение, что и в предыдущем примере.
Как и стиль существования Pooled, так и стиль существования PerW ebRequest, требуют использования немного большего разнообразия задач, нежели простое объявление, но при этом их все равно легко конфигурировать и использовать. Встроенные стили существования Castle Windsor предоставляют обширный и полезный набор стилей существования, который удовлетворяет большинству сценариев. Однако если ни один из этих стилей существования не удовлетворяет специализированным нуждам, мы можем создать пользовательский стиль существования.
Разработка пользовательского стиля существования
В большинстве случаев для наших нужд должны подходить встроенные стили существования Castle Windsor, но если нам нужно что-то особенное, мы можем создать пользовательский стиль существования.
В данном разделе вы увидите, как это сделать. Для начала мы вкратце рассмотрим соответствующий шов, который делает возможным создание пользовательского стиля существования, но быстро перейдем к рассмотрению примера.
П онимание API стиля существования
Вы можете создать пользовательский стиль существования путем реализации интерфейса
ILifestyleManager
: public interface ILifestyleManager : IDisposable
{ void Init(IComponentActivator componentActivator,
IKernel kernel, ComponentModel model); bool Release(object instance); object Resolve(CreationContext context);
}
Одним из немного странных условий реализации
ILifestyleManager является тот факт, что он должен иметь конструктор по умолчанию. Т о есть использовать Constructor
Injection при реализации
ILifestyleManager запрещено. Вместо этого нам предоставляется одна из относительно редких возможностей использовать Method
Injection. Будет вызван метод
Init
, предоставляя среди прочих параметров экземпляр
IKernel
, который можно использовать в качестве Service Locator. Это мне совершенно не подходит, и когда мы будем рассматривать некоторые примеры кода, вы увидите, что такой подход в большей степени усложняет реализацию, чем, если бы было возможным использование Constructor Injection.
Другие методы интерфейса
ILifestyleManager

Resolve и
Release
, но нам следует использовать их в качестве перехватчиков, а не предоставлять свои собственные реализации
Resolve и
Release
– за это отвечает
IComponentActivator
, переданный нам в методе
Init
. Рисунок 10-5 демонстрирует, что для обеспечения возможности управления жизненным циклом каждого компонента мы должны использовать эти методы только для перехвата вызовов
Resolve и
Release

386
Рисунок 10-5:
ILifestyleManager выступает в роли некоторого рода перехватчика, который вызывается вместо упомянутого выше
IComponentActivator
. Предполагается, что реализация
ILifestyleManager использует предоставленный
IComponentActivator для создания экземпляро в объектов. Поскольку
ILifestyleManager располагается посередине, он получает возможность перехватывать каждый вызов и исполнять свою собственную логику стиля существования. Можно повторно использовать экземпляр ы вместо того, чтобы каждый раз вызывать
IComponentActivator
Castle Windsor обеспечивает реализацию по умолчанию
ILifestyleManager в виде класса
AbstractLifestyleManager
. Он реализует интерфейс и предоставляет обоснованную реализацию по умолчанию для большинства методов. Именно этот класс вы будете использовать для реализации шаблонного стиля существования.
Разработка стиля существования Caching
Поскольку Castle Windsor предлагает исчерпывающий набор стандартных стилей существования, трудно придумать хороший пример. Тем не менее, представьте себе, что вам захотелось разработать стиль существования Caching, который держит экземпляр под рукой в течение определенного времени, а затем высвобождает его. Это хороший пример, поскольку он достаточно сложен для демонстрации различных аспектов реализации пользовательского стиля существования, но не так сложен, чтобы на его рассмотрение выделять несколько страниц.
Примечание
Стиль существования Caching – выдуманный пример. Существуют лучшие способы реализации функциональности кэширования, поскольку обычно вы хотите кэшировать не сервисы, а данные, которыми управляют эти сервисы.
Предупрежде ние
Продемонстрированный в данном разделе шаблонный код не принимает во внимание потоко-безопасность, но реальная реализация
ILifestyleManager должна быть потоко- безопасной.
Самый легкий способ реализации пользовательского стиля существования – наследование от
AbstractLifestyleManager
, что продемонстрировано в следующем листинге.

387
Листинг 10-2: Определение пользовательского стиля существования
1.
public partial class CacheLifestyleManager :
2.
AbstractLifestyleManager
3.
{
4.
private ILease lease;
5.
public ILease Lease
6.
{
7.
get
8.
{
9.
if (this.lease == null)
10.
{
11.
this.lease = this.ResolveLease();
12.
}
13.
return this.lease;
14.
}
15.
}
16.
private ILease ResolveLease()
17.
{
18.
var defaultLease = new SlidingLease(TimeSpan.FromMinutes(1));
19.
if (this.Kernel == null)
20.
{
21.
return defaultLease;
22.
}
23.
if (this.Kernel.HasComponent(typeof(ILease)))
24.
{
25.
return this.Kernel.Resolve();
26.
}
27.
return defaultLease;
28.
}
29.
}
Строка 2: Наследование от
AbstractLifestyleManager
Строка 9-13: "Ленивая" загрузка
Строка 23-26: Пытается определить местоположение
ILease
Одна из тех вещей, которые вы получаете благодаря наследованию от
AbstractLifestyleManager
, – реализация метода
Init
. Вы можете переопределить его, но в данном случае это не нужно. Все, что он делает, – сохраняет внедренные сервисы таким образом, чтобы вы могли в дальнейшем получить доступ к ним посредством защищенных свойств.
Для того чтобы реализовать функционально сть, которая решает, когда заканчивается время кэшированного объекта, вам нужен экземпляр
ILease
. Если бы вы могли использовать Constructor Injection, то вы бы запросили
ILease с помощью конструктора, состоящего из трех строк кода (включая граничный оператор). На данный момент вам необходимо 12 строк кода, поскольку вам приходится иметь дело со множеством потенциальных состояний
CacheLifestyleManager
: вызывался ли уже метод
Init
?
Обладает ли
Kernel экземпляром
ILease
?
Вы справляетесь с этим при помощи свойства
Lease
, имеющего отложенную загрузку
(lazy-loaded property). При первом его прочтении он вызывает метод
ResolveLease
, который выясняет, каким должен быть срок аренды. Он использует срок аренды, заданный по умолчанию, но пытается искать альтернативный срок посредством
Kernel
– если
Kernel вообще существует. Я думаю, что это довольно хорошая иллюстрация

388 недостатков Method Injection. Обратите внимание на то, что, если кто-либо прочитает свойство
Lease до вызова метода
Init
, то будет использоваться срок аренды по умолчанию даже в случае, если
Kernel содержит компонент
ILease
. Тем не менее, поскольку Castle W indsor ничего не знает о свойстве
Lease
, при обычном его применении этого не происходит.
П римечание
Интерфейс
ILease
, используемый в данном примере, является пользовательским интерфейсом, определенным для конкретной цели. Это не
System.Runtime.Remoting.Lifetime.ILease
, который имеет аналогичное, но не намного более сложное API.
По сравнению со всеми трудностями, которые вам приходится преодолеть для того, чтобы внедрить зависимость в ваш пользовательский стиль существования, реализация метода
Resolve намного проще. Увидеть это вы можете из приведенного ниже листинга.
Листинг 10-3: Реализация метода
Resolve
1.
private object obj;
2.
public override object Resolve(CreationContext context)
3.
{
4.
if (this.Lease.IsExpired)
5.
{
6.
base.Release(this.obj);
7.
this.obj = null;
8.
}
9.
if (this.obj == null)
10.
{
11.
this.Lease.Renew();
12.
this.obj = base.Resolve(context);
13.
}
14.
return this.obj;
15.
}
Строка 4-8: Выносит объект на просрочку
Строка 11: Продляет срок аренды
Каждый раз, когда
CacheLifestyleManager просят разрешить компонент, он начинает с проверки того, просрочен ли текущий срок аренды. Если срок аренды просрочен,
CacheLifestyleManager высвобождает текущий кэшированный экземпляр и обнуляет его.
Метод
Release явным образом вызывается для базового класса и через него – для
IComponentActivator
, что продемонстрировано на рисунке 10-5. Это важно выполнить, потому что это дает вышеупомянуто й реализации возможность уничтожить экземпляр, если он реализует
IDisposable
Следующее, что необходимо сделать, – проверить, имеет ли кэшированный экземпляр значение null
. Он может иметь null
, если он был высвобожден перед этим, но также может иметь null при первом вызове метода
Resolve
. В обоих случаях вы продлите срок аренды и просите базовую реализацию разрешить компонент для вас. Именно здесь базовый класс вызывает соответствующий метод для
IComponentActivator
В данном пользовательском стиле существования вы переопределяете метод
Release так, чтобы он ничего не делал:

389 public override bool Release(object instance)
{ return false;
}
Это может показаться странным, но является вполне нормальным. Вы должны принять во внимание тот факт, что метод
Release
– это Hook-метод, который является частью
Seam стиля существования Castle W indsor. Вы проинформированы о том, что компонент может быть высвобожден, но это не означает, что вам придется это делать. Для примера стиль существования Singleton по определению никогда не высвобождает свой экземпляр, поэтому он имеет такую же реализацию метода
Release
, как и продемонстрированная ранее.
В случае с
CacheLifestyleManager вы время от времени высвобождает е кэшированный экземпляр, но, как показано в листинге 10-3, вы делаете это в рамках метода
Resolve по необходимости.
CacheLifestyleManager кэширует разрешенный экземпляр до тех пор, пока не истечет срок аренды, а затем разрешает новый экземпляр и продлевает срок аренды. Существует несколько способов реализации логики аренды, но мы рассмотрим только один из них.
Ре ализация Lease
Вам нужна, по крайней мере, одна реализация
ILease для
CacheLifestyleManager
. Срок класса
SlidingLease истекает по окончании фиксированного промежутка времени, но вы могли бы создать другие реализации, срок которых истекает фиксированное количество раз в день, или после того, как они разрешили компонент заданное количество раз.
П римечание
Интерфейс
ILease и класс
SlidingLease
, продемонстрированные в данном разделе, ничего не делают в рамках Castle Windsor, но я хотел продемонстрировать их ради полноты знаний. Вы можете пропустить этот раздел и приступить к чтению информации о том, как зарегистрировать пользовательский жизненный цикл, если вас не интересует
SlidingLease
Следующий листинг демонстрирует реализацию
SlidingLease
Листинг 10-4: Реализация
SlidingLease
1.
public class SlidingLease : ILease
2.
{
3.
private readonly TimeSpan timeout;
4.
private DateTime renewed;
5.
public SlidingLease(TimeSpan timeout)
6.
{
7.
this.timeout = timeout;
8.
this.renewed = DateTime.Now;
9.
}
10.
public TimeSpan Timeout
11.
{
12.
get { return this.timeout; }
13.
}
14.
public bool IsExpired
15.
{
16.
get

390 17.
{
18.
return DateTime.Now >
19.
this.renewed + this.timeout;
20.
}
21.
}
22.
public void Renew()
23.
{
24.
this.renewed = DateTime.Now;
25.
}
26.
}
27.
Строка 16-17: Выносит на просрочку в случае истечения времени ожидания
Строка 22: Продление срока
Класс
SlidingLease реализует
ILease путем отслеживания того, когда был продлен срок аренды. Всякий раз, когда вы запрашиваете у
SlidingLease информацию о том, был ли просрочен срок аренды, он сравнивает текущее время со временем продления и ожидания.
При продлении срока аренды
SlidingLease устанавливает текущее время в качестве времени продления. Я мог бы использовать
TimeProvider
Am bient Cont ext из раздела 4.4.4 вместо
DateTime.Now
, но решил упростить все, насколько это возможно.
Т еперь, когда вы знаете, как реализовать пользовательский стиль существования и любые пользовательские зависимости, которые он может иметь, вам осталось узнать только то, как их использовать.
Конфигурирова ние компонентов с помощью пользовательского стиля существования
Применять
CacheLifestyleManager к компоненту легко, и выполняется это таким же образом, как и определение любого другого стиля существования: container.Register(Component
.For()
.ImplementedBy()
.LifeStyle.Custom());
Вы применяете generic-перегрузку метода
Custom для того, чтобы определить, какой тип
ILifestyleManager использовать. Но существует также перегрузка, которая принимает экземпляр
Type в качестве входного параметра.
До тех пор, пока вы не будете забывать регистрировать также и
ILease
,
CacheLifestyleManager будет использовать заданный по умолчанию срок аренды
SlidingLease с одноминутным таймаутом. Ниже приведен способ регистрации пользовательского
ILease
: container.Register(Component
.For()
.Instance(new SlidingLease(TimeSpan.FromHours(1))));
При помощи данного кода выполняется регистрация экземпляра
SlidingLease с временным интервалом в один час. Вы должны не забывать регистрировать
ILease перед использованием пользовательского стиля существования, поскольку иначе будет использоваться срок аренды по умолчанию.

391
Разработка пользовательского стиля существования для Castle W indsor не особенно сложна. В большинстве случаев класс
AbstractLifestyleManager предоставляет хорошую стартовую точку, и нам нужно только переопределить методы, которые нам особенно важны. Чаще всего это будет метод
Resolve
, хотя для некоторых других методов мы можем оставить их реализации по умолчанию. Т олько в редких случаях нам нужно будет создавать пользовательский стиль существования, поскольку стандартный набор стилей существования Castle Windsor довольно исчерпывающий.
На этом мы заканчиваем свое путешествие по управлению жизненным циклом с помощью
Castle W indsor. Компоненты можно конфигурировать при помощи сочетания стилей существования, и это справедливо даже в тех случаях, когда мы регистрируем составные реализации одной и той же абстракции. Нам еще придется рассмотреть то, как работать с составными компонентами, поэтому давайте сейчас перенаправим наше внимание в это направление.

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

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

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

Используются Decorator'ы
В данном разделе мы рассмотрим каждую из этих ситуаций и увидим, как Castle Windsor поочереди справляется с каждой из них. После прочтения раздела вы должны будете уметь регистрировать и разрешать компоненты даже тогда, когда в дело вступают составные реализации одной и той же абстракции.
Давайте сначала рассмотрим то, как можно обеспечить более разветвленное управление, нежели то, которое предоставляет авт омат ическая интеграция (Auto-Wiring).
В ыбор из составных кандидатов
Автоматическая интеграция – удобный и мощный инструмент, но предоставляет нам меньшие возможности контроля. Пока все абстракции преобразуются в конкретные типы отдельно друг от друга, никаких трудностей не возникает, но как только мы вводим больше реализаций для одного и того же интерфейса, возникает неопределенно сть.
Давайте для начала повторим, как Castle Windsor работает с составными регистрациями одной и той же абстракции.
Ре гистрация составны х реализаций одного и того же се рвиса
Как вы видели в разделе 10.1.2 "Конфигурирование контейнера", вы можете регистрировать составные компоненты одного и того же сервиса: container.Register(Component
.For()
.ImplementedBy()); container.Register(Component
.For()
.ImplementedBy());
Данный пример регистрирует как класс
Steak
, так и класс
SauceBéarnaise для сервиса
IIngredient
. Выигрывает первая регистрация, поэтому, если вы будете разрешать
IIngredient при помощи container.Resolve()
, вы получите экземпляр
Steak
. Т ем не менее, в результате вызова container.ResolveAll()

393 возвращается массив
IIngredient
, содержащий как
Steak
, так и
SauceBéarnaise
. То есть, последующие регистрации не позабыты, но их трудно получить.
П одсказка
Побеждает первая регистрация данного типа. Она определяет регистрацию, используемую для него по умолчанию.
П редупреждение
Если существуют регистрации данного типа, которые не могут быть разрешены в связи с отсутствием зависимостей,
ResolveAll молча игнорирует их и возвращает только те, которые может разрешить. Поскольку не выдается никаких исключений, все это иногда может приводить к некоторым труднопонимаемым багам.
Следующий листинг демонстрирует способ, благодаря которому вы можете предоставлять подсказки, которые могут впоследствии использоваться для осуществления выбора из составных кандидатов.
Листинг 10-5: Присваивание имен компонентам container.Register(Component
.For()
.ImplementedBy()
.Named("meat")); container.Register(Component
.For()
.ImplementedBy()
.Named("sauce"));
Каждой регистрации вы можете присвоить уникальное имя, которое позднее может использоваться для того, чтобы отличить этот компонент от других компонентов.
Имея представленные в листинге 10-5 именованные компоненты, вы можете разрешить и
Steak
, и
SauceBéarnaise следующим образом: var meat = container.Resolve("meat"); var sauce = container.Resolve("sauce");
Заметьте, что вы применяете тот же самый идентификатор, который использовали для присваивания имени компоненту во время регистрации.
Если вы примете как должное то, что вам следует всегда разрешать сервисы в единственном Com position Root, вы уже не будете сталкиваться с такой неопределенностью на данном уровне.
П одсказка
Если вы обнаружили, что вызываете метод
Resolve со специальным идентификатором, подумайте, можете ли вы сделать свою технологию менее неопределенно й.
Т ем не менее, вы можете использовать именованные компоненты для того, чтобы выбрать между составными альтернативами при конфигурировании зависимостей данного сервиса.

394
Ре гистрация именованны х зависимостей
Насколько бы полезной ни была автоматическая интеграция, иногда вам необходимо переопределять обычное поведение таким образом, чтобы обеспечить более разветвленное управление зависимостями. Но может случиться и то, что вам нужно будет обратиться к неопределенному API. В качестве примера рассмотрите приведенный ниже конструктор: public ThreeCourseMeal(ICourse entrée,
ICourse mainCourse, ICourse dessert)
В данном примере у вас имеется три одинаково типизированных зависимости, каждая из которых представляет отличную от других сущность. В большинстве случаев вы хотите преобразовать каждую зависимость к отдельному типу. Следующий листинг демонстрирует, каким образом вы могли бы зарегистрировать преобразования
ICourse
Листинг 10-6: Регистрация именованных course
'ов container.Register(Component
.For()
.ImplementedBy()
.Named("entrée")); container.Register(Component
.For()
.ImplementedBy()
.Named("mainCourse")); container.Register(Component
.For()
.ImplementedBy()
.Named("dessert"));
Согласно листингу 10-6 вы регистрируете три именованных компонента, преобразуя
Rilettes в регистрацию с именем "entrée" (напоминаю американским читателям, что это то же самое, что и starter или appetizer – то, что возбуждает аппетит, придает вкус),
CordonBleu
– в регистрацию с именем "
mainCourse
", а
MousseAuChocolat
– в регистрацию с именем "
dessert
".
Имея такую конфигурацию, вы теперь можете зарегистрировать класс
ThreeCourseMeal
, как это продемонстрировано в следующем листинге.
Листинг 10-7: Переопределение автоматической интеграции container.Register(Component
.For()
.ImplementedBy()
.ServiceOverrides(new
{ entrée = "entrée", mainCourse = "mainCourse", dessert = "dessert"
}));
Вы можете явным образом обеспечить переопределения для тех параметров (или свойств), к которым хотите явно обращаться. В случае класса
ThreeCourseMeal вам нужно обращаться ко всем трем параметрам конструктора. Т ем не менее, в других случаях вы можете захотеть переопределить только один из нескольких параметров; это тоже возможно. Метод
ServiceOverrides позволяет вам применять анонимный

395 типизированный объект, который указывает, какие параметры необходимо переопределить. Если вы не хотите использовать безымянные типы, то другие перегрузки методов
ServiceOverrides дают нам возможность использовать массив специализированных экземпляров
ServiceOverride или
IDictionary
Используя анонимный тип, вы сравниваете параметры, которые хотите переопределить, с именованной регистрацией. В первом случае вы сравниваете имя параметра entrée с регистрацией, имеющей имя "ent rée". В данном случае имена параметров идентичны именам регистраций, но это не обязательно. Остальные параметры преобразуются похожим способом.
П редупреждение
Несмотря на то, что анонимный тип может выглядеть строго типизированным, если он используется указанным образом, то представляет собой еще одну группу "волшебных строк". В конце концов, он конвертируется в словарь имен и значений. Имена свойств анонимный типа должны совпадать с именами параметров соответствующего конструктора. Если в конструкторе вы измените имя параметра, то
ServiceOverride перестанет функционировать до того момента, пока вы не исправите имя параметра, поэтому не полагайтесь на эту возможность чаще, чем это требуется.
Поскольку метод
ServiceOverrides зависит от сравнения имен параметров и сконфигурированных переопределений по текстовому принципу, лучше слишком сильно на него не полагаться. Если вы чувствуете необходимость использовать этот метод только для того, чтобы справиться с неопределенно стью, то для избавления от этой неопределенности лучше всего сконструировать API. Это чаще всего приводит к более полноценному дизайну.
В следующем разделе вы увидите, как выполнить рефакторинг текущего класса
ThreeCourseMeal для получения более общей реализации и в то же время для избавления от присущей неопределенности. Сделать это можно, разрешив в обеде произвольное количество блюд, – но это принуждает вас к осознанию того, как Castle Windsor подключает списки и последовательности.
Интеграция последовательностей
В разделе 6.4.1 мы обсуждали, как Constructor Injection выступает в роли системы оповещения при нарушениях принципа единственной ответственности. Урок, извлеченный из того раздела, – вместо того, чтобы рассматривать constructor over-injection как слабую сторону паттерна Constructor Injection, мы должны скорее порадоваться тому, что constructor over-injection делает сомнительную композицию столь очевидной.
Когда дело доходит до DI-контейнеров и неопределенности, мы видим аналогичную взаимосвязь. DI-контейнеры, в общем случае, не справляются с неопределенность ю столь грациозно. Несмотря на то, что мы можем заставить DI-контейнер, например, Castle
W indsor, справляться с неопределенност ью, он чаще всего делает это с трудом. Чаще всего это говорит о том, что мы могли бы улучшить конструкцию нашего собственного кода.
П одсказка

396
Если конфигурировать определенную составляющу ю вашего API в Castle Windsor сложно, подумайте, можете ли вы сделать ваше API более явным и четким. Это не только облегчит его конфигурацию в рамках Castle W indsor, но также, вероятнее всего, улучшит общую конструкцию вашего кода.
Вместо того чтобы чувствовать себя стесненными Castle Windsor, мы должны принять его соглашения и позволить ему провести нас к улучшенной и более последовательной конструкции. В данном разделе мы рассмотрим пример, который продемонстрирует, как можно выполнить рефакторинг таким образом, чтобы избавиться от неопределенно сти, а также покажем, как Castle Windsor работает с последовательност ями, массивами и списками.
Ре факторинг с целью получения лучшего course
В разделе 10.3.1 "Выбор из составных кандидато в" вы увидели, как
ThreeCourseMeal и присущая ему неопределенно сть вынудили нас отказаться от автоматической интеграции и вместо нее использовать явный
ServiceOverride
. Это должно было дать вам намек на пересмотр конструкции API.
Простое обобщение приводит нас к реализации
IMeal
, которая принимает в качестве параметров произвольное количество экземпляров
ICourse вместо явных трех, как было в случае с классом
ThreeCourseMeal
: public Meal(IEnumerable courses)
Обратите внимание на то, что вместо требования о наличии в конструкторе трех отдельных экземпляров
ICourse
, единичная зависимость экземпляра
IEnumerable
позволяет вам обеспечить любое количество блюд в классе
Meal
– от 0 до … бесконечности! Это решает проблему неопределенности, поскольку теперь существует только одна зависимость. Кроме того, это улучшает API и реализацию, обеспечивая единственный универсальный класс, который может моделировать множество различных видов обедов, от простого обеда с единственным блюдом до сложного, состоящего из 12 блюд.
Имея регистрацию course
'ов, продемонстрированну ю в листинге 10-6, вы сможете автоматически разрешать
IMeal
, если зарегистрируете его следующим образом: container.Register(Component
.For()
.ImplementedBy());
Т ем не менее, когда вы пытаетесь разрешить
IMeal
, контейнер выдает исключение.
Несмотря на то, что исключение совсем ни о чем нам не говорит, причина в том, что вы не сообщили контейнеру, как он должен разрешать
IEnumerable
. Давайте сделаем обзор некоторых различных доступных вариантов.
Конфигурирова ние массивов
Castle W indsor отлично понимает массивы. Поскольку массивы реализуют
IEnumerable
, вы можете явным образом сконфигурировать массив для параметра конструктора courses
. Это можно сделать способом, похожим на синтаксис, который вы

397 видели в листинге 10-7. В следующем листинге вы увидите те же самые courses, определенные в виде сервисов.
Листинг 10-8: Явное определение массива сервисов
1.
container.Register(Component
2.
.For()
3.
.ImplementedBy()
4.
.ServiceOverrides(new
5.
{
6.
courses = new[]
7.
{
8.
"entrée",
9.
"mainCourse",
10.
"dessert"
11.
}
12.
}));
Строка 6-10: Переопределение параметра courses
Аналогично листингу 10-7 вы используете метод
ServiceOverrides
, когда хотите переопределить автоматическу ю интеграцию для конкретных параметров. В данном примере вы хотите явным образом сконфигурировать параметр конструктора courses для класса
Meal
. Поскольку этот параметр является
IEnumerable
, вы должны теперь задать последовательность сервисов
ICourse
Поскольку массивы реализуют
IEnumerable
, вы можете определить массив именованных сервисов. Осуществляете вы это путем создания массива имен сервисов.
Эти имена идентичны именам, присвоенным каждой регистрации в листинге 10-6, а Castle
W indsor настолько добр, что преобразует этот массив имен сервисов в массив экземпляров
ICourse в рабочей среде. Все это аналогично листингу 10-7 за одним лишь исключением – fluent registration API по своей природе понимает и преобразует массивы имен сервисов в массивы сервисов.
Несмотря на то, что рефакторинг от
ThreeCourseMeal к
Meal казался шагом в правильном направлении, кажется, вы ничего не сделали касательно громоздкости конфигурации.

1   ...   23   24   25   26   27   28   29   30   ...   43


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