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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница31 из 43
1   ...   27   28   29   30   31   32   33   34   ...   43
helper) интерфейс, который вводится для реализации
CacheLifecycle
. Первоначально этот интерфейс был введен в разделе 10.2.3 "Разработка пользовательского стиля существования" и не имеет никакого отношения ни к
StructureMap, ни к любому другому DI-контейнеру.
П римечание
Чтобы увидеть пример реализации
ILease
, взгляните на раздел 10.2.3.
Вместо того чтобы сохранять экземпляр
ILease напрямую в приватном поле, вы незамедлительно оборачиваете его в пользовательскую реализацию интерфейса
IObjectCache под названием
LeasedObjectCache
. Это тот кэш, который возвращается методом
FindCache
П римечание
Сравните конструктор из листинга 11-4 с намного более сложным кодом листинга 10-2.
Данное сравнение ясно иллюстрирует превосходство Constructor Injection над Method
Injection.
Несмотря на то, что
CacheLifecycle предоставляет исходный интерфейс
ILifecycle
, истинная реализация обеспечивается при помощи пользовательского класса
LeasedObjectCache
, который реализует интерфейс
IObjectCache
StructureMap уже предоставляет реализацию
IObjectCache под названием
MainObjectCache
. К несчастью,
MainObjectCache не имеет ни одного виртуального члена, который мы могли бы переопределит ь для того, чтобы реализовать стиль существования
Caching. Вместо этого мы можем обернуть
MainObjectCache пользовательским
LeasedObjectCache
. В следующем листинге продемонстрирован конструктор.
Листинг 11-5: Конструирование
LeasedObjectCache private readonly IObjectCache objectCache; private readonly ILease lease; public LeasedObjectCache(ILease lease)
{ if (lease == null)
{ throw new ArgumentNullException("lease");
} this.lease = lease; this.objectCache = new MainObjectCache();
}
В конструкторе
LeasedObjectCache вы используете стандартный Constructor Injection для того чтобы внедрить экземпляр
ILease
LeasedObjectCache
– это Decorator для
MainObjectCache
, поэтому вы создаете экземпляр и присваиваете его приватному полю.
Обратите внимание на то, что поле objectCache объявлено как
IObjectCache
, поэтому вы

433 могли бы просто расширить класс
LeasedObjectCache перегруженным конструктором, который позволял бы вам внедрять любую реализацию
IObjectCache из вне.
Комбинация обернутого
IObjectCache и члена
ILease приближает реализацию класса
LeasedObjectCache к тривиальной. Следующий листинг демонстрирует реализацию важных методов
Get и
Set
, а остальная реализация руководствуется тем же самым проектом.
Листинг 11-6: Реализация методов
Get и
Set public object Get(Type pluginType, Instance instance)
{ this.CheckLease(); return this.objectCache.Get(pluginType, instance);
} public void Set(Type pluginType, Instance instance, object value)
{ this.objectCache.Set(pluginType, instance, value); this.lease.Renew();
} private void CheckLease()
{ if (this.lease.IsExpired)
{ this.objectCache.DisposeAndClear();
}
}
Когда StructureMap вызывает метод
Get
, вы, в первую очередь, удостоверяетесь в том, что кэш не содержит какие-либо просроченные экземпляр ы. При возврате этим методом результата вы можете быть уверены в том, что, если обернутый кэш содержит запрашиваемый экземпляр, вы сможете безопасно его вернуть.
Наоборот, при вызове метода
Set вы тотчас же делегируете этот метод обернутому объекту-кэшу. Поскольку вы понимаете, что StructureMap использует
IObjectCache так, как показано на рисунке 11-5, вы знаете, что метод
Set вызывается только тогда, когда контейнер создает новый экземпляр, поскольку ни один экземпляр кэша недоступен. Это означает, что экземпляр, переданный при помощи параметра value
, представляет собой только что созданный экземпляр, поэтому вы можете безопасно обновить срок аренды.
Вспомогательный метод
CheckLease вызывается многими реализациями члена
IObjectCache способами, аналогичными методу
Get
. Он игнорирует обернутый кэш, если срок его аренды истек.
Т еперь, когда вы знаете, как реализовать пользовательский стиль существования и любые пользовательские зависимости, которыми он может обладать, вам остается только узнать, как его применять.
Конфигурирова ние компонентов с помощью пользовательского стиля существования
Использовать
CacheLifecycle при конфигурировании компонента легко, а выполняется это таким же самым способом, которым бы вы конфигурировали любой другой стиль существования:

434 var lease = new SlidingLease(TimeSpan.FromMinutes(1)); var cache = new CacheLifecycle(lease); container.Configure(r => r
.For()
.LifecycleIs(cache)
.Use());
При помощи данного кода контейнер конфигурируется таким образом, что
CacheLifecycle используется для интерфейса
IIngredient с одноминутным промежутком. В пределах одной минуты вы можете запросить столько диаграмм объектов, сколько только пожелаете, и при этом будете получать обратно тот же самый
SauceBéarnaise всякий раз, когда в диаграмме будет содержаться экземпляр
IIngredient
. По окончании одноминутного промежутка последующие запросы будут получать новый экземпляр
SauceBéarnaise
Следует отметить, что способ, при помощи которого реализуется
CacheLifecycle
, может следующим образом использоваться для связывания вместе нескольких экземпляров, имеющих один и тот же срок аренды: container.Configure(r =>
{ r.For().LifecycleIs(cache).Use(); r.For().LifecycleIs(cache).Use();
});
Это будет приводить к тому, что экземпляры
ICourse и
IIngredient будут просрочены и вновь созданы в одно и то же время. Иногда это может быть нужно, а иногда и нет.
Альтернативный вариант – использовать два отдельных экземпляра
CacheLifecycle
. Как показывает следующий листинг, такая возможность также позволяет вам использовать два различных таймаута.
Листинг 11-7: Использование разных стилей существования Cache для каждого
Instance container.Configure(r => r
.For()
.LifecycleIs( new CacheLifecycle( new SlidingLease(
TimeSpan.FromHours(1))))
.Use()); container.Configure(r => r
.For()
.LifecycleIs( new CacheLifecycle( new SlidingLease(
TimeSpan.FromMinutes(15))))
.Use());
Первый cache задается с одночасовым таймаутом. Не важно, на сколь долгий или сколь малый промежуток времени вам необходим
IIngredient
, в течение одночасового периода вы будете получать один и тот же экземпляр. По истечении одного часа старый экземпляр перестает учитываться, а новый экземпляр беспорядочно используется в течение следующего часа.
Cache для
ICourse
– это другой экземпляр, сконфигурированный с 15-минутным таймаутом. В течение этих 15 минут вы будете получать один и тот же экземпляр, но когда они истекут, будет использоваться новый экземпляр. Стоит отметить, что даже

435 когда время
ICourse истекает,
IIngredient продолжает существовать благодаря своему более длительному сроку аренды. Несмотря на то, что
ICourse и
IIngredient используют один и тот же стиль существования type
, они имеют разные расписания.
В листинге 11-7 вы использовали разные таймауты, но при этом в обоих случаях применяли тип
SlidingLease
. Это не является обязательным условием, вы могли бы использовать две совершенно разные реализации
ILease для каждого экземпляра.
Реализация пользовательского стиля существования для StructureMap не столь сложна. В теории она может казаться довольно сложной, но если бы вы рассмотрели этот процесс в интегрированной среде разработки (IDE), то быстро поняли бы, что реализация пользовательского стиля существования состоит всего лишь из двух классов, в которых самый сложный метод (
CheckLease
) имеет только один оператор if и состоит из двух строк кода.
Все-таки необходимость реализации пользовательского стиля существования для
StructureMap должна встречаться довольно редко. Исчерпывающий набор встроенных стилей существования должен удовлетворять нашим повседневным нуждам.
На этом мы завершаем наше путешествие по управлению жизненным циклом в рамках
StructureMap. Компоненты должны конфигурироваться при помощи сочетания стилей существования, и это справедливо, даже когда мы регистрируем составные реализации одной и той же абстракции. Нам еще нужно будет рассмотреть то, как работать с составными компонентами, поэтому давайте направим наше внимание в этом направлении.

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

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

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

Используются Decorator'ы.
В данном разделе мы рассмотрим каждую из этих ситуаций и увидим, как StructureMap поочереди справляется с каждой из них. После прочтения раздела вы должны будете уметь регистрировать и разрешать компоненты даже тогда, когда в дело вступают составные реализации одной и той же абстракции.
Давайте сначала рассмотрим то, как можно обеспечить более разветвленное управление, нежели то, которое предоставляет нам автоматическая интеграция (Auto-W iring).
В ыбор из составных кандидатов
Автоматическая интеграция – удобный и мощный инструмент, но предоставляет нам меньшие возможности контроля. Пока все абстракции преобразуются в конкретные типы отдельно друг от друга, никаких трудностей не возникает, но как только мы вводим большее количество реализаций для одного и того же интерфейса, возникает неопределенность.
Давайте для начала повторим, как StructureMap работает с составными регистрациями одной и той же абстракции.
Конфигурирова ние составных реализаций одного и того же плагина
Как вы уже видели в разделе 11.1.2 "Конфигурирование контейнера", вы можете конфигурировать составные плагины одного и того же сервиса: container.Configure(r =>
{ r.For().Use(); r.For().Use();
});
Данный пример кода регистрирует как класс
Steak
, так и класс
SauceBéarnaise вместе с плагином
IIngredient
. Выигрывает последняя реализация, поэтому, если вы разрешаете
IIngredient посредством container.GetInstance()
, вы получите

437 экземпляр
Steak
. Т ем не менее, вызов container.GetAllInstances()
вернет
IList
, который содержит как
Steak
, так и
SauceBéarnaise
. То есть, последующие конфигурации не позабыты, но их трудно получить.
П одсказка
Выигрывает последняя конфигурация данного типа. Она задает экземпляр по умолчанию для этого типа.
Если есть сконфигурированные экземпляры плагина, которые не могут быть разрешены при вызове
GetAllInstances
, StructureMap выдает исключение, объясняющее, что есть зависимости, которые не удовлетворяют заданным условиям. Это сообразно поведению метода
GetInstance
, но отличается от поведения Castle W indsor или MEF.
Следующий листинг демонстрирует, как можно обеспечить появление подсказок, которые впоследствии могут использоваться для осуществления выбора между различными сконфигурированными экземпляра ми.
Листинг 11-8: Присваивание имен экземплярам container.Configure(r =>
{ r.For()
.Use()
.Named("sauce"); r.For()
.Use()
.Named("meat");
});
Вы можете присвоить каждому сконфигурированно му экземпляру уникальное имя, которое в дальнейшем можете применять для того, чтобы отличать друг от друга схожие экземпляр ы.
При наличии именованных экземпляров из листинга 11-8, вы можете разрешать и
Steak
, и
SauceBéarnaise следующим образом: var meat = container.GetInstance("meat"); var sauce = container.GetInstance("sauce");
Обратите внимание на то, что вы передаете тот же самый идентификатор, который использовали, когда присваивали имя экземпляру при конфигурировании.
Предполагая, что вам всегда следует разрешать сервисы в единственном Composition Root, вы не должны ожидать, что на данном уровне столкнетесь с такой неопределенностью.
П одсказка
Если вы обнаружили, что вызываете метод
GetInstance с конкретным идентификатором, подумайте, можете ли вы изменить свой подход так, чтобы он стал менее неопределенным.
Можно использовать именованные экземпляры для того, чтобы сделать выбор между составными альтернативами при конфигурировании зависимости для данного плагина.

438
Конфигурирова ние именованны х зависимостей
Для тех же целей, которые обеспечивает автоматическая интеграция, нам иногда нужно переопределять обычное поведение, чтобы обеспечить более расширенный контроль над тем, какие зависимости, куда направляются. Иногда возникают ситуации, когда нам нужно обращаться к неопределенному API. В качестве примера рассмотрите приведенный ниже конструктор: public ThreeCourseMeal(ICourse entrée,
ICourse mainCourse, ICourse dessert)
В данном примере у вас имеется три идентично типизированных зависимости, каждая из которых представляет сущность, отличную от остальных. В большинстве случаев вам необходимо преобразовать каждую зависимость к отдельному типу. Следующий листинг демонстрирует, как вы могли бы решить конфигурировать преобразования
ICourse
Листинг 11-9: Конфигурирование именованных course
'ов container.Configure(r => r
.For()
.Use()
.Named("entrée")); container.Configure(r => r
.For()
.Use()
.Named("mainCourse")); container.Configure(r => r
.For()
.Use()
.Named("dessert"));
Как и в листинге 11-8 вы регистрируете три именованных компонента, преобразуя
Rilettes в экземпляр под названием "
entrée
",
CordonBleu
– в экземпляр с именем "
mainCourse
", а
MousseAuChocolat
– в экземпляр под названием "
dessert
".
При такой конфигурации вы теперь можете регистрировать класс
ThreeCourseMeal так, как показано в следующем листинге.
Листинг 11-10: Переопределение автоматической интеграции container.Configure(r => r
.For()
.Use()
.Ctor("entrée").Is(i => i.TheInstanceNamed("entrée"))
.Ctor("mainCourse").Is(i => i.TheInstanceNamed("mainCourse"))
.Ctor("dessert").Is(i => i.TheInstanceNamed("dessert")));
Как обычно, выражение конфигурирования вы начинаете с преобразования интерфейса
IMeal к конкретному
ThreeCourseMeal
. Но в дальнейшем вы расширяете выражение при помощи метода
Ctor
. Метод
Ctor
(сокращение от constructor
) повзоляет вам выражать то, как должен преобразовываться параметр конструктора данного типа. В случае, когда для данного типа существует только один параметр, можно использовать перегрузку, в которой вам не приходится передавать имя параметра. Тем не менее, поскольку

439
ThreeCourseMeal имеет три параметра
ICourse
, вам необходимо идентифицировать параметр по его имени, "
entrée
".
Метод
Ctor возвращает объект, который позволяет вам задать то, как будет заполняться параметр конструктора. Метод
Is позволяет вам использовать
IInstanceExpression
для отбора именованного экземпляра, что является еще одним примером паттерна Nested Closure. Эти же выражения вы в дальнейшем можете повторить и для следующих двух параметров.
П римечание
В этом примере я присвоил экземплярам конфигурации такие же имена, как и параметрам, но это не является необходимым. Я мог назвать экземпляр ы так, как мне пожелается, несмотря на то, что имена параметров, очевидно, привязаны к именам реальных параметров конструктора.
П редупреждение
Идентифицировать параметры по их именам удобно, но это не приводит к безопасному рефакторингу. Если мы переименуем параметр, мы можем разрушить конфигурацию (это зависит от вашего инструмента рефакторинга).
Переопределение автоматической интеграции путем явного преобразования параметров в именованные экземпляр ы – общепринятый вариант работы. Сделать это мы можем, даже если мы конфигурируем именованные экземпляры с помощью одного выражения, а конструктор – с помощью совершенно другого выражения, поскольку единственная идентификация, связывающая именованный экземпляр с параметром, – это имя. Т ак сделать можно всегда, но, когда нам нужно управлять большим количеством имен, такой вариант может оказаться хрупким.
И нте грация ссылок на экземпляры
Всякий раз, когда у нас есть возможность определить экземпляры и конструктор в одном выражении, мы можем сделать это более изящно. Ниже приведенный листинг показывает, как это можно сделать.
Листинг 11-11: Использование ссылок на экземпляры для переопределения автоматической интеграции
1.
container.Configure(r =>
2.
{
3.
var entrée =
4.
r.For().Use();
5.
var mainCourse =
6.
r.For().Use();
7.
var dessert =
8.
r.For().Use();
9.
r.For()
10.
.Use()
11.
.Ctor("entrée").Is(entrée)
12.
.Ctor("mainCourse").Is(mainCourse)
13.
.Ctor("dessert").Is(dessert);
14.
});
Строка 3,5,7: Ссылки на экземпляр ы

440
Строка 11-13: Использование ссылок на экземпляры
До настоящего момента мы игнорировали тот факт, что типичная цепочка методов
For
/
Use возвращает какой-то результат, поскольку для нас это было бесполезно. Но возвращаемые значения являются экземплярами
SmartInstance
, которые можно использовать в качестве ссылок на конфигурации, которые вы делали до этого. Вместо имен экземпляров, которые вам пришлось использовать в листинге 11-10, вы можете использовать эти ссылки прямо с одной из множества перегрузок метода
Is
, сравнивая каждую локальную переменную с соответствующим именованным параметром конструктора.
Несмотря на то, что данная возможность позволяет нам избавиться от имен экземпляро в, у нас все еще остаются "волшебные" строки, которые идентифицируют параметры конструктора. Данное API зависит от текстового совпадения между конфигурацией и именами параметров, поэтому оно является недолговечным, и такого API стоит по возможности избегать. Если мы чувствуем, что это нужно использовать только для того, чтобы справиться с неопределенност ью, наилучшее решение – разработать API, которое позволит нам избавиться от этой неопределенно сти. Чаще всего это приводит еще и к лучшему дизайну в целом.
В следующем разделе вы увидите, как можно использовать менее неопределенный и более гибкий подход, при котором разрешается любое количество блюд в обеде. Для этого вы должны узнать, как StructureMap работает со списками и последовательностями.
Интеграция последовательностей
В разделе 10.3.2 "Разработка пользовательского стиля существования" мы обсуждали, как выполнить рефакторинг явного класса
ThreeCourseMeal к более универсальному классу
Meal
, который обладает приведенным ниже конструктором: public Meal(IEnumerable courses)
В данном разделе мы будем рассматривать то, как можно сконфигурировать StructureMap таким образом, чтобы интегрировать экземпляр ы
Meal с соответствующими зависимостями
ICourse
. После рассмотрения этого вопроса вы должны будете приобрести хорошее понимание тех возможностей, которые доступны в случае необходимости конфигурирования экземпляров, имеющих последовательности зависимостей.
Автоматическое инте грирование последовательностей
StructureMap довольно хорошо разбирается в последовательно ст ях. Если мы хотим использовать все сконфигурированные экземпляры данного плагина, то в этом случае нам нужна именно автоматическая интеграция. К примеру, при наличии сконфигурированных экземпляров
ICourse из листинга 11-9 можно сконфигурировать плагин
IMeal следующим образом: container.Configure(r => r.For().Use());
Обратите внимание на то, что в данном примере приведено совершенно стандартное преобразование абстракции к конкретному типу. StructureMap будет автоматически понимать конструктор
Meal и определять, что подходящее направление действий – разрешение всех экземпляров
ICourse
. При разрешении
IMeal вы получаете экземпляр

441
Meal наряду с экземплярами
ICourse
, описанными в листинге 11-9:
Rillettes
,
CordonBleu и
MousseAuChocolat
Примечание
Сравните удобство автоматической интеграции последовательностей в StructureMap и раздел 10.3.2 "Разработка пользовательс кого стиля существования", который демонстрирует, как сложно обеспечить такую же функциональность в Castle Windsor.
StructureMap автоматически обрабатывает последовательнос ти и, пока мы не укажем обратное, он делает то, что мы и предполагали: разрешает последовательност ь зависимостей всех зарегистрированных экземпляров данного типа. Т олько в тех случаях, когда нам необходимо отобрать всего лишь несколько экземпляро в из огромного набора, нам нужно выполнить большее количество действий. Давайте посмотрим, как это сделать.
О тбор нескольких экземпляров из большого набора
Чаще всего используемая StructureMap по умолчанию стратегия внедрения всех экземпляров является корректной методикой, но, как показывает рисунок 11-6, могут возникать ситуации, когда нам нужно отобрать несколько сконфигурированных экземпляров из большого набора всех сконфигурированных экземпляров.
Рисунок 11-6: В ситуации, приведенной слева, мы хотим явно выбрать только конкретные зависимости из большого перечня всех сконфигурированных экземпляров. Данная ситуация отличается от той, которая приведена справа – здесь мы без разбора отбираем все экземпляр ы.
Когда мы ранее позволили StructureMap автоматически интегрировать все сконфигурированные экземпляр ы, это привело к ситуации, изображенной в правой части рисунка 11-6. Если мы хотим сконфигурировать экземпляр так, как это показано в левой части рисунка, мы должны явным образом указать то, какие экземпляр ы должны использоваться.

442
Когда у нас есть возможность конфигурировать зависимости и потребителя в одном вызове метода
Configure
, мы можем использовать ссылочные экземпляр ы так, как вы это видели в листинге 11-11. Следующий листинг демонстрирует эквивалентну ю конфигурацию сценария, при котором в конструкторе предполагается наличие последовательност и зависимостей.
Листинг 11-12: Использование ссылок на экземпляры для внедрения последовательност ей container.Configure(r =>
{ var entrée = r.For().Use(); var entrée1 = r.For().Use(); var mainCourse = r.For().Use(); var dessert = r.For().Use(); r.For().Use()
.EnumerableOf()
.Contains(entrée, mainCourse, dessert);
});
Аналогично коду из листинга 11-11 вы присваиваете переменную каждому экземпляру, который возвращается методом
Use
. Обратите внимание на то, что вы конфигурируете
четыре экземпляра
ICourse
, даже если вы используете только три из них для экземпляра
IMeal
. Т ем не менее, вам может понадобиться преобразовать
ICourse к
LobsterBisque для некоторых других целей, непродемонстриро ванных здесь. Поскольку вы не используете результирующую переменную entrée1
, вы могли бы ее полностью опустить, но я решил ее включить для того, чтобы код был последовательным.
Поскольку конструктор
Meal принимает в качестве входного параметра
IEnumerable
, вы можете использовать метод
EnumerableOf для обозначения последовательност и экземпляров
ICourse
, явно определенных в методе
Contains
, где вы передаете три ссылки на экземпляры, которые собираетесь использовать.
Этот подход хорошо использовать тогда, когда есть возможность сконфигурировать все соответствующие экземпляры
ICourse в том же блоке кода, в котором выполняется конфигурация
IMeal
. Это не всегда возможно. Может случаться, что конфигурация экземпляров
ICourse разделяется на несколько различных регистров из различных сборок. В этом случае мы можем прибегнуть к способу обращения к ним по имени.
Следующий листинг демонстрирует один из таких способов.
Листинг 11-13: Внедрение именованных экземпляров в последовате льност и container.Configure(r => r
.For()
.Use()
.EnumerableOf().Contains(i =>
{ i.TheInstanceNamed("entrée"); i.TheInstanceNamed("mainCourse"); i.TheInstanceNamed("dessert");
}));
При наличии набора именованных экземпляров, аналогичных тем, которые были созданы в листинге 11-9, вы можете ссылаться на каждый именованный экземпляр при конфигурировании экземпляра
IMeal
. Как и в листинге 11-12, вы используете цепочку методов
EnumerableOf/Contains для обозначения последовательности зависимостей. На

443 этот момент у вас нет переменных
Instance
, поэтому вы должны искать их по имени.
Перегрузка метода
Contains дает вам возможность использовать Nested Closure, который объявляет то, какие именованные экземпляры вы хотите внедрить в экземпляр
Meal
StructureMap понимает последовательно сти. До тех пор пока не появляется необходимость отбирать только некоторые экземпляры из всех плагинов данного типа, StructureMap автоматически все делает правильно. Автоматическая интеграция применяется не только для единичных экземпляров, но также и для последовательнос тей. А контейнер преобразует последовательнос ть во все сконфигурированные экземпляр ы соответствующего типа.
Потребители, которые полагаются на последовательности зависимостей, могут быть самыми интуитивно понятными пользователю составными экземплярами одной и той же зависимости. Но перед тем как мы полностью отойдем от данной темы, нам необходимо рассмотреть последний (и, возможно, слегка неожиданный) случай, когда в дело вступают составные экземпляры.
Интеграция Decorator'ов
В разделе 9.1.2 "Паттерны и принципы механизма перехвата" мы обсуждали то, насколько паттерн проектирования Decorator полезен при реализации сквозных сущностей. По определению Decorator'ы представляют собой составные типы одной и той же абстракции.
У нас есть, по крайней мере, две реализации абстракции: сам Decorat or и обернутый тип.
Если бы мы помещали Decorat or'ы в стек, то у нас было бы еще больше реализаций.
Это еще один пример составных регистраций одного и того же сервиса. В отличие от предыдущих разделов эти регистрации не являются концептуально равносильными, а зависят друг от друга. В данном разделе вы увидите, как сконфигурировать StructureMap таким образом, чтобы он мог работать с этим паттерном. Существует множество способов конфигурирования Decorat or'а, и мы рассмотрим три различных способа достижения одного и того же результата. Каждый из них имеет свои собственные достоинства и недостатки.
С оздание обертки при помощи ссы лок на экземпляры
Давайте посмотрим, как можно сконфигурировать класс
Breading
, который является
Decorator'ом
IIngredient
. Этот класс использует Constructor Injection для получения экземпляра, который необходимо обернуть: public Breading(IIngredient ingredient)
Для того чтобы сделать панированную телячью котлету, вам хотелось бы обернуть
VealCutlet
(еще один
IIngredient
) в класс
Breading
. Один из способов это сделать – использовать ссылки на экземпляры в рамках единичного метода
Configure
: container.Configure(r =>
{ var cutlet = r.For().Use(); r.For().Use()
.Ctor().Is(cutlet);
});

444
Как вы уже видели в листингах 11-11 и 11-12, можно использовать возвращаемое методом
Use значение для того, чтобы перехватить ссылку на экземпляры. Переменная cutlet представляет собой сконфигурированно е преобразование
IIngredient в
VealCutlet
. И вы можете использовать этот факт для того, чтобы объявить, что эта переменная и есть
Instance
, который можно использовать в параметре
IIngredient конструктора класса
Breading
. Поскольку выигрывает последняя конфигурация,
Breading Instance на данный момент является используемым по умолчанию
Instance
Когда вы попросите контейнер разрешить
IIngredient
, он вернет объект, основанный на используемом по умолчанию
Instance
. Это и есть
Breading Instance
, в котором вы предоставили дополнительный намек на то, что он должен разрешить cutlet Instance для параметра
IIngredient класса
Breading
. В результате мы получаем экземпляр
Breading
, содержащий экземпляр
Cutlet
Передача объектов безопаснее передачи строк, поэтому нам следует отдавать предпочтение использованию данной методики всякий раз, когда у нас есть возможность конфигурировать Decorator и обернутый тип в одном вызове метода. Тем не менее, это не всегда возможно.
С оздание обертки при помощи име нованных экземпляров
Иногда нам приходится прибегать к именам
Instance
, потому что мы конфигурируем вложенные соучастники в разных вызовах метода, возможно, даже в разных регистрах, реализованных в отдельных библиотеках. В таких случаях мы не можем передавать объекты, а должны полагаться на строки, даже если их легко испортить.
Давайте предположим, что вы уже сконфигурировали
VealCutlet следующим образом: container.Configure(r => r
.For()
.Use()
.Named("cutlet"));
Поскольку вы знаете, что имя экземпляра – cutlet
, вы можете использоват ь его для конфигурирования класса
Breading
: container.Configure(r => r
.For()
.Use()
.Ctor()
.Is(i => i.TheInstanceNamed("cutlet")));
Как и в листингах 11-10 и 11-13 вы используете перегрузку метода
Is
, которая дает вам возможность предоставить блок кода, идентифицирующий именованный экземпляр. И снова вы видите паттерн Nested Closure в действии.
Если вы сравните два предыдущих примера, то не заметите, что они похожи. В обоих случаях вы использовали метод
Ctor
, олицетворяющий параметр конструктора.
Единственное отличие заключает ся в том, как вы идентифицируете параметр с методом
Is
Цепочка методов
Ctor
/
Is имеет преимущество, заключающееся в том, что мы можем использовать ее для указания единственного параметра конструктора, даже если

445 рассматриваемый конструктор имеет более одного параметра. Все параметры, которые мы не сможем сконфигурировать при помощи метода
Ctor
, будут автоматически интегрированы на основании алгоритмов, используемых StructureMap по умолчанию. Это полезно, если мы хотим сконфигурировать только один из нескольких параметров.
Т ем не менее, такой вариант не является строго типизированным. Нет ни какой гарантии, что рассматриваемый конструктор имеет параметр указанного типа. Он мог его иметь, но потом мы изменили конструкцию, и теперь он принимает параметры другого типа.
Компилятор об этом не знает, поскольку, когда мы вызываем метод Ctor с конкретным аргументом типа, он доверяет нам.
Другой вариант предлагает более строго типизированный подход.
С оздание обертки при помощи делегатов
Вместо того чтобы ссылаться на параметр конструктора по типу или имени, мы можем записать строго типизированный блок кода, в котором используется конструктор.
Несмотря на то, что такой подход также имеет недостатки, к которым мы позднее вернемся, его достоинство заключается в том, что он строго типизирован и поэтому более безопасен с точки зрения момента конструирования.
Это выглядит слегка абстрактным, поэтому давайте рассмотрим пример, демонстрирующий, как сконфигурировать
Cotoletta следующим образом: container.Configure(r => r
.For().Use()
.EnrichWith(i => new Breading(i)));
Метод
EnrichWith
– член generic-класса
SmartInstance
, который возвращается методом
Use
. В данном случае вы вызываете метод
Use с аргументом типа
VealCutlet
Этот метод возвращает экземпляр
SmartInstance
. Метод
EnrichWith принимает в качестве параметра делегат, который, в свою очередь, принимает в качестве входного параметра
VealCutlet и возвращает объект.
Вы можете сравнить этот делегат с блоком кода, который принимает в качестве входного параметра
VealCutlet
. Компилятор делает вывод, что переменная i
– это экземпляр
VealCutlet
, поэтому вы теперь можете реализовать блок кода посредством вызова конструктора
Breading с переменной
VealCutlet
Когда вы попросите контейнер разрешить
IIngredient
, он сначала создаст экземпляр
VealCutlet
, а затем передаст этот экземпляр в качестве входной информации в блок кода, который вы определили с помощью метода
EnrichWith
. При выполнении блока кода экземпляр
VealCutlet передается в конструктор
Breading
, и возвращается экземпляр
Breading
Достоинство данного подхода заключается в том, что в блоке кода вы записываете код, в котором используется конструктор
Breading
. Этот код является такой же строкой кода, как и любая другая строка кода, поэтому она проверяется компилятором. Это обеспечивает вас уверенностью в том, что если метод
Configure компилируется, то
VealCutlet будет корректно обернута.

446
Несмотря на то, что строгая типизированност ь безопаснее, ее сложнее поддерживать.
Если вы впоследствии решите добавить еще один параметр в конструктор
Breading
, блок кода больше не будет компилироваться, и вы должны будете вручную справиться с данной проблемой. Это было бы не нужно, если бы вы использовали метод
Ctor
, поскольку StructureMap смог бы отсортировать новый параметр благодаря автоматической интеграции.
Как вы уже видели, существует несколько способов конфигурирования Decorat or'ов.
Строго типизированный подход более безопасен, но для него может потребоваться более сложное сопровождение. Более слабо типизированное API – более гибкое, и дает
StructureMap возможность справиться с изменениями нашего API, но ценой менее слабой типовой безопасности.
П римечание
В данном разделе мы не обсуждали механизм перехвата во время выполнения. Несмотря на то, что StructureMap имеет
Seam
'ы, которые разрешают механизм перехвата, он не обладает встроенной поддержкой динамически создаваемых прокси. Можно использовать эти
Seam
'ы для того, чтобы использовать другую библиотеку (например, Castle Dynam ic
Proxy) для создания таких классов. Но поскольку они не являются частью StructureMap, их обсуждение выходит за рамки данной главы.
StructureMap позволяет нам работать с составными экземплярами несколькими различными способами. Мы можем конфигурировать экземпляры как альтернативы друг другу, как пиры, разрешенные в виде последовательностей, или как иерархические
Decorator'ы. В большинстве случаев StructureMap поймет, что делать, но мы всегда можем явно определить, каким образом скомпоновать сервисы, если нам нужен более явный контроль.
Кроме того, это может быть необходимо в тех случаях, когда нам приходится иметь дело с
API, которые отклоняются от Constructor Injection. До настоящего момента вы наблюдали, как конфигурировать экземпляры, включая то, как задавать стили существования и как работать с составными компонентами. Но до настоящего момента вы позволяли контейнеру подключать зависимости, явным образом предполагая, что все компоненты используют Constructor Injection. Это не всегда так. В следующем разделе мы сделаем обзор того, как работать с классами, экземпляр ы которых должны создаваться особыми способами.

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

448 container.Configure(r => r
.For()
.Use()
.Ctor()
.Is(Spiciness.Hot));
В разделе 11.3 "Работа с составными компонентами" вы не раз видели, каким образом можно использовать метод
Ctor
для того, чтобы переопределить автоматическу ю интеграцию для конкретного параметра конструктора. В данном разделе вы косвенным образом устанавливаете, что, подразумевается, что конструктор
ChiliConCarne имеет только один параметр
Spiciness
. В противном случае вызов метода
Ctor()
будет неоднозначным, и вам придется передавать также и имя параметра.
Метод
Ctor
возвращает
SmartInstance
, который имеет разнообразные методы.
Существует 5 перегрузок метода
Is
, а одна из них дает возможность предоставить экземпляр соответствующего типа. Аргументом типа
T
в данном случае является
Spiciness
, поэтому вы предоставляете
Spiciness.Hot в качестве конкретного значения.
Как мы уже обсуждали в разделе 11.3 "Работа с составными компонентами", использование метода
Ctor
имеет свои преимущества и недостатки. Если нам нужна более строго типизированная конфигурация, которая вызывает конструктор или статическую фабрику, мы также можем это сделать.
Создание объектов с помощью блока кода
Экземпляры некоторых классов не могут создаваться посредством открытых конструкторов. Поэтому в таких случаях для создания экземпляро в типов мы должны использовать некоторого рода фабрику. Это всегда проблематично для DI-контейнеров, поскольку по умолчанию они присматривают открытые конструкторы.
Рассмотрим приведенный ниже пример конструктора для открытого класса
JunkFood
: internal JunkFood(string name)
Несмотря на то, что класс
JunkFood является открытым классом, его конструктор является внутренним. Очевидно, экземпляр ы
JunkFood должны создаваться посредством статического класса
JunkFoodFactory
: public static class JunkFoodFactory
{ public static IMeal Create(string name)
{ return new JunkFood(name);
}
}
С точки зрения StructureMap, это API проблематично, поскольку не существует каких- либо определенных и опубликованных соглашений, касающихся статических фабрик.
Поэтому ему необходима помощь, которую вы можете предоставить посредством блока кода, который StructureMap может исполнить для того, чтобы создать экземпляр:

449 container.Configure(r => r
.For()
.Use(() =>
JunkFoodFactory.Create("chicken meal")));
К этому времени цепочка методов
For
/
Use должна быть вам уже знакома. Т ем не менее, в данном случае вы используете перегрузку метода
Use
, отличную от той, которую вы использовали ранее. Эта перегрузка позволяет вам передавать
Func
, что вы делаете посредством блока кода, который вызывает статический метод
Create класса
JunkFoodFactory
П одсказка
Если вы хотите разрешить класс
ChiliConCarne из раздела 11.4.1 "Конфигурирование простейших зависимостей" строго типизированным способом, то можете использовать данную перегрузку
Use для непосредственного вызова конструктора.
После завершения написания кода, который создает экземпляр, можете ли вы ответить, почему такой подход в любом случае лучше непосредственного вызова кода? Используя блок кода внутри оператора
For
/
Use
, вы кое-что, таким образом, приобретаете:

Преобразуете
IMeal к
JunkFood

Стиль существования все еще можно конфигурировать. Несмотря на то, что для создания экземпляра будет вызываться блок кода, он может и не вызываться всякий раз, когда запрашивает ся данный экземпляр. За исключением тех случаев, когда вы используете стиль существования Unique, иногда вместо блока кода может использоват ься кэшированный экемпляр.
В общем, существует пять различных перегрузок метода
Use
. Для задания конкретного типа можно использовать generic-версию, но остальные перегрузки позволяют нам передавать конкретный экземпляр или блоки кода, создающие конкретный экземпляр.
Последним общепринятым отклонением от Constructor Injection, которое мы рассмотрим в данном разделе, является Propert y Injection.
Интеграция с помощью Property Injection
Property Injection является не столь четко определенной формой механизма внедрения зависимостей, поскольку компилятор не принуждает вас присваивать значение свойству, доступному для записи. Это справедливо и для StructureMap, который будет пропускать доступные для записи свойства до тех пор, пока мы явно не попросим его что-нибудь с ними сделать.
Рассмотрите приведенный ниже класс
CaesarSalad
: public class CaesarSalad : ICourse
{ public IIngredient Extra { get; set; }
}
Согласно общепринятому заблуждению в салат "Цезарь" входит курица. По существу салат "Цезарь" является салатом, но, поскольку с курицей он вкуснее, многие рестораны предлагают возможность добавления в него курицы в качестве дополнительного

450 ингредиента. Класс
CaesarSalad моделирует такую возможность посредством доступного для записи свойства под названием
Extra
Если вы конфигурируете только класс
CaesarSalad
, явно не обращаясь к свойству
Extra
, то этому свойству не будет присвоено значение. Вы все равно можете разрешать экземпляр, но свойство
Extra будет иметь значение по умолчанию, которое ему присвоил конструктор (если только это имеет место).
Существует несколько способов, с помощью которых можно сконфигурировать
CaesarSalad таким образом, чтобы свойство
Extra заполнялос ь соответствующим образом. Один из таких способов – использование ссылок на экземпляр ы. Этот способ вы уже ранее несколько раз видели в данной главе: container.Configure(r =>
{ var chicken = r.For().Use(); r.For().Use()
.Setter().Is(chicken);
});
Из нескольких предыдущих примеров вы можете вспомнить, что метод
Use возвращает
Instance
, который вы можете помнить как переменную. В листинге 11-10 и во многих последующих примерах вы использовали метод
Ctor
для того, чтобы обозначить параметр конструктора определенного типа. М етод
Setter
работает аналогичным образом, но только для свойств. Вы передаете экземпляр chicken в метод
Is
, чтобы заставить StructureMap присвоить значение свойству при построении экземпляра.
Когда вы будете на основании этой конфигурации разрешать
ICourse
, вы получите обратно экземпляр
CaesarSalad
, свойству
Extra которого будет присвоен экземпляр
Chicken
. Это предоставляет вам возможность дифференциро ванного управления конкретными свойствами конкретных типов. API, которое в большей степени основано на соглашениях, предоставляет нам возможность утверждать, что мы хотим, чтобы
StructureMap использовало все свойства данного типа для Property Injection. К примеру, мы могли бы установить, что все заданные свойства
IIngredient должны внедряться вместе с соответствующим экземпляром.
В случае
CaesarSalad вы можете выразить это следующим образом: container.Configure(r => r.For().Use()); container.Configure(r => r.For().Use()); container.Configure(r => r.FillAllPropertiesOfType());
Благодаря методу
FillAllPropertiesOfType вы можете установить, что всем доступным для записи свойствам типа
IIngredient должно быть присвоено значение. StructureMap будет использовать экземпляр по умолчанию, сконфигурированный для
IIngredient
, поэтому при разрешении
ICourse вы получите экземпляр
CaesarSalad со свойством
Extra равным
Chicken
FillAllPropertiesOfType будет заполнять любое доступное для записи свойство указанного типа, поэтому, если другие конкретные классы также обладают доступными

451 для записи свойствами того же типа, в них также будут внедрены сконфигурированные экземпляр ы. Это может быть целесообразным, если мы руководствуемся соглашением, которое использует Property Injection для определенных типов.
В данном разделе вы увидели, как можно использовать StructureMap для работы с более сложными API создания экземпляров. Для того чтобы задать конкретные экземпляры или блоки кода, которые будут применяться для создания экземпляро в, можно использовать множество перегрузок методов
Use и
Is
. Вы также видели, что Property Injection можно конфигурировать непосредственно при конфигурировании экземпляров или в виде соглашения для конкретного типа.

452 11.5. Резюме
Данная глава представляет собой дегустационное меню StructureMap и его возможностей.
Мы соотносим принципы и паттерны остальной части книги с API контейнера
StructureMap. StructureMap – это старейший из доступных в .NET DI-контейнеров, но этот факт ничего не говорит ни о его возрасте, ни о доминирующем использовании вложенных
замыканий (Nested Closures), ни о его конфигурационном API, которое обладает свойством типовой безопасности, ни о возможности поиска типов в нем на основании соглашений.
Использование паттерна Nested Closure, возможно, является одной из самых отличительных особенностей StructureMap. Для его использования необходимо хорошо разбираться в делегатах и блоках кода.
Начать работать со StructureMap довольно легко. Он поддерживает автоматическую интеграцию и автоматически определяет, каким образом создавать конкретные типы, даже если они не были явным образом сконфигурированы. Это означает, что вы можете сконцентрироваться на преобразовании абстракций в конкретные типы, и, когда вы закончите это преобразование, вы сможете разрешать диаграммы объектов. API, используемое для поиска типов, даже дает вам возможность сконфигурировать множество сервисов посредством всего нескольких строк кода, используя при этом для конфигурирования подход, основанный на соглашениях.
Несмотря на то, что нам не нужно конфигурировать конкретные сервисы, мы можем захотеть сделать это в тех ситуациях, когда нам нужно изменить стиль существования. По умолчанию используется стиль существования Per Graph, поэтому всякий раз, когда у нас имеются потоко-безопасные сервисы, мы можем потенциально увеличить эффективность путем конфигурирования их в виде Singleton'ов. Для этого нужен определенный шаг, несмотря на то, что можно было бы выразить это во время поиска типа посредством использования пользовательского соглашения о регистрации.
Не гарантируется, что экземпляры будут отслеживаться контейнером, поэтому
StructureMap не предлагает никакого API для высвобождения конкретной диаграммы объектов. Это эффективно предотвращает утечки памяти для обычных классов, но, с другой стороны, почти гарантирует утечки памяти для устраняемых зависимостей.
Поэтому важно реализовыват ь все зависимости таким образом, чтобы они самостоятельно управляли всеми внутренними использованиями устраняемых типов.
StructureMap свободно обрабатывает последовательнос ти зависимостей. Когда класс зависит от последовательности экземпляров одного и того же типа, StructureMap будет автоматически интегрировать экземпляр со всеми экземплярами того типа, который имеет зависимость. Поведение StructureMap интуитивно понятно, поэтому нам нужно всего лишь явно выполнить определенное действие в тех ситуациях, когда нам требуется отобрать только некоторое подмножество экземпляров из всех доступных экземпляров.
Несмотря на то, что мы можем явным образом сконфигурировать Decorator'ы,
StructureMap не имеет никаких соглашений по интеграции Decorator'ов и не обладает возможностями динамического перехвата. Существуют
Seam
'ы, которые могут использоваться для того, чтобы интегрировать в StructureMap динамическое прокси API, если нам нужно интегрировать Decorator'ы.

453
Поскольку StructureMap столь глубоко полагается на вложенные замыкания, неудивительно, что многие методы конфигурации обладают перегрузками, дающими нам возможность использовать блок кода, который будет вызываться при создании экземпляров. Несмотря на то, что эти перегрузки не являются необходимыми в тех ситуациях, когда регистрируемые нами классы используют Constructor Injection, мы можем использовать их, если один или более одного из наших классов должны создаваться особым образом.
StructureMap – исчерпывающий DI-контейнер, который предлагает широкий набор продвинутых возможностей. Он имеет отличное поведение по умолчанию и может с легкостью использоваться, особенно в тех случаях, когда дело касается автоматической интеграции конкретных типов или последовательно стей. С другой стороны, он не обладает возможностями динамического перехвата и не может уничтожать устраняемые зависимости. Эти очевидные недостатки являются, главным образом, результатом философии проектирования. Если мы никогда не будем реализовывать устраняемые сервисы и вместо динамического перехвата решим использовать явные Decorat or'ы, то в этом случае StructureMap является отличным вариантом, поскольку он использует эти ограничения для того, чтобы упростить для пользователей все остальное.

454 12. Spring.NET
Меню:

Знакомство со Spring.NET

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

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

Конфигурирование сложных API
В предыдущих главах вы наблюдали за тем, как применять к Castle W indsor и
StructureMap принципы и паттерны, описанные в частях 1-3. В данной главе мы то же самое будем делать для DI-контейнера Spring.NET.
Наряду с Castle W indsor и StructureMap контейнер Spring.NET принадлежит к числу DI- контейнеров .NET "первого поколения". Появился он в 2006 году и даже на сегодняшний момент все еще поддерживает .NET 1.1. Это порт Java-версии Spring Fram ework, являющегос я большим и исчерпывающим фреймворком приложений, который имеет дело со множеством различных аспектов программного обеспечения – не только Object
Com position. DI-контейнер – один из множества доступных во фреймворке компонентов, но его можно отлично использовать и как самостоятельный компонент, не обращаясь при этом к другим компонентам Spring.NET .
В этой главе мы сконцентрируемся на рассмотрении DI-контейнера Spring.NET и опустим остальные компоненты фреймворка. Как в предыдущих и последующих главах, мы будем изучать то, как можно использовать Spring.NET для применения принципов и паттернов, описанных в частях 1-3. Рисунок 12-1 демонстрирует структуру данной главы.
Рисунок 12-1: Данная глава разделена на 4 раздела. Первый раздел знакомит нас с API
Spring.NET и должен рассматриваться в качестве предпосылки следующих трех разделов.
Каждый из четырех разделов данной главы может быть прочитан независимо от остальных разделов.
Первый раздел обеспечивает полноценное знакомство со Spring.NET и демонстрирует, как конфигурировать и разрешать объекты. Каждая из трех последующих глав затрагивает паттерны применения, которые требуют к себе дополнительного внимания. Вы можете читать их по порядку или пропустить некоторые и прочитать только те, которые вам интересны.
Данная глава должна дать вам возможность приступить к работе, а также справиться с большинством общепринятых проблем, которые могут возникнуть при ежедневном использовании Spring.NET. Это не окончательная трактовка Spring.NET, поскольку

455 всецелое его рассмотрение заняло бы несколько глав или, может быть, даже целую книгу.
В любом случае весь фреймворк Spring.NET в данной книге не рассматривается. К рассматриваемому вопросу относится только DI-контейнер Spring.NET, поэтому, когда я использую название Spring.NET, я ссылаюсь конкретно на контейнер.
Вы можете прочитать эту главу, не читая при этом остальные главы части 4, специально для того, чтобы познакомиться со Spring.NET, или можете прочитать ее совместно с остальными главами части 4, чтобы сравнить DI-контейнеры. Цель данной главы – показать, как Spring.NET связан с паттернами и принципами, описанными в частях 1-3, и как он их реализует.
12.1. Знакомство с Spring.NET
12.2. Управление жизненным циклом
12.3. Работа с составными компонентами
12.4. Конфигурирование сложных API
12.5. Резюме

456 12.1. Знакомство с Spring.NET
Из этого раздела вы узнаете, где можно взять Spring.NET, что вы при этом получите и как начать его использовать. Кроме того, мы рассмотрим универсальные варианты конфигурирования. Таблица 12-1 предоставляет основополагающую информацию, которая, скорее всего, понадобится вам для того, чтобы приступить к работе со
Spring.NET.
Таблица 12-1: Краткая информация о Spring.NET
Вопрос
Ответ

1   ...   27   28   29   30   31   32   33   34   ...   43


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