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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница22 из 43
1   ...   18   19   20   21   22   23   24   25   ...   43
()
.SingleInstance(); builder.Register((c, p) => new SqlProductRepository(connectionString))
.As
(); builder.RegisterType
()
.As();
Вы хотите, чтобы
ContractMapper имел стиль существования Singleton, поэтому вы определяете это путем вызова метода
SingleInstance
. Когда дело касается
SqlProductRepository
, все становится немного сложнее, поскольку экземпляр
SqlProductRepository должен быть Transient, но внедренная строка соединения должна быть Singleton. Вы можете достичь этого, извлекая connectionString из конфигурации приложения (не продемонстрировано, но похоже на то, как это делалось ранее) и используя эту внешнюю переменную в рамках замыкания (closure), которое вы применяете для определения конструктора. Поскольку connectionString является внешней переменной, она остается неизменной в рамках множества вызовов конструктора. Обратите внимание на то, как безоговорочно вы сделали и
SqlProductRepository
, и
ProductManagementService
, не указывая при этом стиль существования.
Несмотря на то, что этот пример описывает, как определять стили существования с помощью Autofac, другие DI-контейнеры имеют для той же цели приблизительно такое же API.
Возможность точной настройки стиля существования каждой зависимости важна с точки зрения представления, но также важна и с точки зрения функционально сти. Например, паттерн проектирования Mediator (Посредник) полагается на совместно используемый директор, посредством которого взаимодейству ют несколько компонентов. Это работает только, когда Mediator совместно используется рассматриваемыми сотрудниками.
До настоящего момента мы обсуждали то, как инверсия управления намекает на то, что потребители не могут управлять жизненными циклами своих зависимостей, поскольку они, очевидно, не контролируют процесс создания объектов, и поскольку .NET использует

292
механизм сборки м усора (garbage collection) пользователи не могут явным образом уничтожать объекты.
Т аким образом, вопрос "а как насчет устраняемых зависимостей?" остается без ответа.
Сейчас мы обратим свой взор к этому деликатно му вопросу.

293 8.2. Работа с устраняемыми зависимостями
Несмотря на то, что .NET – это управляемая платформа, имеющая сборщик мусора, она все еще может взаимодействовать с неуправляемым кодом (unmanaged code). Когда это происходит, .NET код взаимодействуе т с неуправляемой памятью, которая не уничтожается сборщиком мусора. Для предотвращения утечки памяти нам нужен механизм, с помощью которого мы бы детерминированно выпускали неуправляему ю память. Это и является ключевой целью интерфейса
IDisposable
Вероятнее всего, что некоторые реализации зависимостей будут содержать в себе неуправляемые ресурсы. К примеру, ADO.NET связи являются устраняемыми, поскольку они стремятся использовать неуправляемую память, поэтому реализации, связанные с базами данных, например, репозитории, скорее всего, сами являются устраняемыми.
Как нам следует моделировать устраняемые зависимости? Должны ли мы также позволять абстракциям быть устраняемыми? Все это могло бы выглядеть так: public interface IMyDependency : IDisposable { }
Т ехнически это возможно, но не особенно предпочтительно, поскольку является примером smell(дурно пахнущее)-проектиро вания, который указывает на leaky- абстракцию ("дырявую" абстракцию).
Интерфейс …, в больш инстве случаев, не должен быт ь устраняемым. Не сущ ест вует
способа, с помощью которого т от , кт о определяет инт ерфейс, смог бы предвидет ь все
возможные реализации эт ого интерфейса – вы всегда можете столкнуться с
устраняемой реализацией практ ически любого инт ерфейса.
Николас Блумхардт, участник форума Common Context Adapt ers
Если вы стремитесь добавить
IDisposable в ваш интерфейс, то это возможно, поскольку вы имеете ввиду конкретную реализацию. Но вы не должны использовать это знание при проектировании интерфейса. Все это усложнило бы реализацию интерфейса для других классов и внесло бы в абстракцию неопределенность. Кто отвечает за уничтожение устраняемых зависимостей? Может ли за это отвечать потребитель?
Использование устраняемых зависимостей
В целях рассуждений представьте себе, что у нас есть устраняемая абстракция, подобная абстрактному классу
OrderRepository
: public abstract class OrderRepository : IDisposable
Как класс
OrderRepository должен взаимодейство ват ь с зависимостью? Большинство руководств по проектированию (включая FxCop и встроенный в Visual Studio Code
Analysis) настаивало бы на том, что, если бы класс содержал устраняемый ресурс в качестве члена класса, то он сам должен был бы реализовывать
IDisposable и избавляться от ресурса, подобного следующему:

294 protected virtual void Dispose(bool disposing)
{ if (disposing)
{ this.repository.Dispose();
}
}
Но, оказывается, что это совсем плохая идея, поскольку член repository был внедрен первоначально, и он может совместно использоваться другими потребителями, как это продемонстрировано на рисунке 8-6.
Рисунок 8-6: Единичный экземпляр
SqlOrderRepository внедряется и в
OrderService
, и в
SupplierReorderPolicy
. Эти два экземпляра используют одну и ту же зависимость.
Если
OrderService уничтожит свой внедренный
OrderRepository
, то он разрушит зависимость
SupplierReorderPolicy
, и когда
SupplierReorderPolicy попытается использовать эту зависимость, возникнет исключение.
Было бы менее опасным не удалять внедренный репозиторий, но это означает, что мы, по существу, игнорируем тот факт, что абстракция является устраняемой. Другими словами, объявление абстракции как унаследованной от
IDisposable не приносит никакой пользы.
Кроме того, могут встречаться сценарии, в которых нам нужно сообщать о начале и окончании краткосрочных областей применения, а
IDisposable иногда используется для этой цели. Перед тем, как рассмотреть то, как
Composer может управлять жизненным циклом устраняемой зависимости, нам следует обсудить то, как работать с такими недолговечными устраняемыми объектами.
Создание недолговечны х устраняемых объектов
Многие API стандартной библиотеки классов .NET используют
IDisposable
, чтобы сообщить о том, что конкретная область применения перестала существовать. Одним из самых выдающихс я примеров является W CF-прокси.
WC F-прокси и IDisposable
Все авто-генерируемые WCF-прокси реализуют
IDisposable
, поэтому важно не забывать вызывать метод
Dispose
(или
Close
) для прокси, как только это становится возможным.
Многие связывания при отправке первого запроса автоматически создают сессию для сервиса, и эта сессия задерживается в сервисе до тех пор, пока не заканчивается ее время, или до тех пор, пока она явным образом не уничтожается.
Если мы забудем уничтожить наши W CF-прокси после использования, количество сессий будет увеличиваться до тех пор, пока мы не достигнем лимита одновременных

295 соединений из одного и того же источника. Когда мы достигнем лимита, возникнет исключение. Слишком большое количество сессий также привносит в сервис лишнюю нагрузку, поэтому уничтожение WCF-прокси по мере возможности является очень важным.
Чтобы быть полностью технически корректными, нам не приходится вызывать метод
Dispose для WCF-прокси. Использование метода
Close приведет к тому же самому результату.
Важно не забывать, что при использования
IDisposable для таких целей не нужно указывать leak-абстракцию, поскольку эти типы не всегда в первую очередь являются абстракциями. С другой стороны, некоторые из них являются leak-абстракциями; а в таких случаях, как должны мы поступать с ними?
К счастью, после уничтожения объекта мы не можем его заново использовать. Это означает, что если мы хотим вызвать то же самое API снова, мы должны создать новый экземпляр. К примеру, это хорошо подходит, когда мы используем WCF-прокси или
ADO.NET команды: мы создаем прокси, вызываем его операции и избавляемся от него, как только работа с ним завершается. Как мы можем совместить это с механизмом внедрения зависимостей, если мы считаем, что устраняемые абстракции являются leak- абстракциями?
Как и всегда может быть полезным скрытие ненужных деталей в интерфейсе. Если мы обратимся к WP F приложению из раздела 7.4, то в данном случае мы спрятали WCF- прокси в интерфейсе
IProductManagementAgent
П римечание
Интерфейс
IProductManagementAgent наиболее значителен в листинге 7-10, но если не брать во внимание этот листинг, мы не рассматривали этот интерфейс подробно. В сущности, такой агент занимает такое же самое место, как и репозиторий, но много лет назад я приобрел привычку называть компоненты доступа к данным Sm art Clients агентами, а не репозиториями.
С точки зрения
MainViewModel ниже приведено то, как вы удаляете товар: this.agent.DeleteProduct(productId);
Вы просите внедренный agent удалить товар.
MainViewModel может безопасно хранить ссылку на агента, поскольку интерфейс
IProductManagementAgent не унаследован от
IDisposable
При рассмотрении WCF реализации этого интерфейса формируется другая картина. Ниже приведена реализация метода
DeleteProduct
: public void DeleteProduct(int productId)
{ using (var channel = this.factory.CreateChannel())
{ channel.DeleteProduct(productId);
}
}

296
Класс
WcfProductManagementAgent не является изменчивым, но имеет внедренную абстрактную фабрику, которую вы можете использовать для создания канала. Канал – это еще одно слово для обозначения W CF-прокси, и это еще и авто-генерируемый клиентский интерфейс, который вы получаете бесплатно, когда создаете ссылку на сервис с помощью
Visual Studio или svcutil.exe. Поскольку этот интерфейс унаследован от
IDisposable
, вы можете завернуть его в оператор using
Вы используете канал для удаления товара. Когда вы покидаете область применения using
, канал уничтожается.
Но постойте! Не утверждал ли я, что устраняемые абстракции являются leaky- абстракциями? Да, утверждал, но мне приходится сопоставлять прагматические сущности с принципами. В данном случае
WcfProductManagementAgent
, абстрактная фабрика
IProductChannelFactory и
IProductManagementServiceChannel определены в одной и той же WCF-специфичной библиотеке, выделенно й на рисунке 8-7.
Рисунок 8-7: Помимо других типов библиотека
ProductWcfAgent содержит реализацию
IProductManagementAgent и поддерживаемые им типы.
WcfProductManagementAgent использует
IProductChannelFactory для создания экземпляро в
IProductManagementServiceChannel
, которые являются устраняемыми. Несмотря на то, что они могут рассматриваться, как leak-абстракции, они не "протекают" слишком далеко, поскольку все потребители и реализаторы находятся в той же самой сборки.
Каждый раз при вызове метода для класса
WcfProductManagementAgent он незамедлительно открывает новый канал и уничтожает его после использования. Его жизненный цикл весьма краток, поэтому я называю такую устраняемую абстракцию недолговечным устраняемым объектом.
Обратите внимание на то, что недолговечный устраняемый объект никогда не внедряется в потребителя. Вместо него внедряется абстрактная фабрика, и вы используете эту фабрику для того, чтобы контролировать жизненный цикл недолговечн ых устраняемых объектов.
Обобщая все выше сказанное, устраняемые абстракции являются leaky-абстракциями.
Иногда мы должны принимать такую "протечку" для того, чтобы избежать багов
(например, отвергнутые WCF соединения); но если мы так поступаем, то можем сделать все возможное, чтобы эта "протечка" не распространилась по всему приложению.

297
На данный момент мы рассмотрели то, как использовать устраняемые зависимости.
Давайте обратим наше внимание на то, как мы можем обслуживать их и управлять ими вместо потребителей.
У правление устраняемыми зависимостями
Поскольку я столь непреклонно настаиваю на том, что устраняемые абстракции являются leaky-абстракциями, можно сделать вывод, что абстракции не должны быть устраняемыми. С другой стороны, реализации иногда бывают устраняемыми, и если мы не уничтожим их правильно, в нашем приложении будет происходить утечка ресурсов.
Кто-то должен их удалять.
П одсказка
Старайтесь реализовывать сервисы так, чтобы они не содержали ссылок на устраняемые объекты, а создавали и уничтожали их по требованию, как это проиллюстрировано на рисунке 6-3. Это упрощает процесс управления памятью, поскольку сервис может быть уничтожен сборщиком мусора наряду с другими объектами.
Как всегда данная ответственность ложится на
Composer
(например, DI-контейнер).
Composer лучше всех остальных знает, в какой момент он создает устраняемый экземпляр, поэтому он также знает, что этот экземпляр необходимо уничтожить. Для
Composer проще хранить ссылку на устраняемый экземпляр и вызывать его метод
Dispose в подходящий момент.
Проблема заключается в определении подходящего времени для уничтожения. Как мы узнаем, в какой момент все потребители вышли за рамки области применения?
До тех пор, пока кто-нибудь не скажет нам, что все потребители вышли за рамки области применения, мы это не узнаем, но чаще всего наш код располагается внутри некоторого рода контекста с вполне определенным жизненным циклом и событиями, которые и сообщают нам, в какой момент завершается конкретная область применения. Т аблица 8-1 демонстрирует области применения технологий, которые мы рассматривали в главе 7.
Таблица 8-1: Т очки входа и выхода для различных .NET Framework'ов
Т ехнологи я
Точка входа
Точка выхода
Консольны е приложени я
Main
Main
ASP.NET
MVC
IControllerFactory.CreateControl ler
IControllerFactory.ReleaseControl ler
W CF
IInstanceProvider.GetInstance
IInstanceProvider.ReleaseInstance
W PF
Application.OnStartup
Application.OnExit
ASP.NET
Constructors**
,
Page_Load
IDisposable.Dispose**
,
Page_Unload
PowerShell
Constructors**
IDisposable.Dispose**

298
Мы можем использовать различные точки выхода для того, чтобы сообщить
Composer
, что ему необходимо уничтожить все зависимости данного объекта. Поэтому задача отслеживания таких зависимостей и их стилей существования, а также принятие решения по вопросу, должны ли те или иные зависимости высвобождат ься, остается в компетенции
Composer
Вы свобождение зависимостей
Высвобождение диаграммы объектов – это не то же самое, что и ее уничтожение. Это сигнал, сообщающий
Composer
, что центральная часть диаграммы выходит за рамки области применения, поэтому если сама центральная часть реализует
IDisposable
, то она должна быть уничтожена. Но зависимости этой центральной части могут использоваться также и другими центральными частями, поэтому
Composer может принять решение о сохранении некоторых из них, поскольку он знает, что другие объекты все еще полагаются на эти зависимости. Рисунок 8-8 иллюстрирует данную последовательность событий.
Рисунок 8-8: Когда
Composer получает запрос о преобразовании объекта, он собирает все зависимости запрашиваемого объекта. В данном примере запрашиваемый объект имеет три зависимости, и две из них являются устраняемыми. Одна из этих устраняемых зависимостей также используется и другими потребителями, поэтому она является повторно используемой, тогда как остальные зависимости проиллюстрированы только в одном месте. При получении запроса о высвобождении объекта
Composer уничтожает приватную устраняемую зависимость и разрешает неустранимой зависимости и самому объекту выйти за рамки области применения. Единственной взаимосвязью с повторно используемой зависимостью остается тот факт, что она внедрена в запрашиваемый объект; но так как она является повторно используемой, она пока не уничтожается.

299
Чтобы высвободить зависимости,
Composer должен отследить все устраняемые зависимости, которые он когда-либо обслуживал, и тех потребителей, которые используют эти зависимости, таким образом, чтобы он мог уничтожить их, когда будет высвобожден последний потребитель.
П одсказка
Если вы когда-либо работали со счетчиками референсов (или имели опыт работы с багами, являющимися следствием плохой реализации), то вы оцените, каким сложным может быть процесс сохранения обозначений для всех зависимостей и их потребителей.
Это именно тот момент, когда вам может пригодиться DI-контейнер, поскольку он берет заботу обо всем этом на себя. Используйте DI-контейнер, а не разрабатывайте свой собственный код отслеживания жизненного цикла. Реализация процесса управления жизненным циклом DI-контейнером гарантированно является более протестированной, нежели все то, что вы можете создать в пределах разумных временных рамок.
Давайте вернемся к примеру WCF сервиса из раздела "Управление жизненным циклом с помощью контейнеров". Оказывается, в листинге 8-2 есть баг, потому что, как демонстрирует рисунок 8-9,
SqlProductRepository реализует
IDisposable
Рисунок 8-9:
SqlProductRepository реализует
IDisposable
, потому что содержит устраняемый ресурс. Он также наследуется от абстрактного класса
ProductRepository
, который не реализует
IDisposable
Код из листинга 8-2 создает новые экземпляр ы
SqlProductRepository
, но никогда не высвобождает эти экземпляры. Это будет приводить к утечке ресурсов, поэтому давайте устраним этот баг с помощью новой версии специализиро ванного контейнера.
Для начала учтите, что контейнер должен уметь обслуживать множество одновременных запросов, поэтому ему приходится связывать каждый экземпляр
SqlProductRepository с создаваемым им
IProductManagementService
. Контейнер использует
Dictionary
, называемые репозиториями, для того, чтобы отслеживат ь эти связи. Следующий листинг демонстрирует, как контейнер преобразовывает запросы экземпляров
IProductManagementService
Листинг 8-4: Связывание устраняемых зависимостей с разрешенной центральной частью public IProductManagementService ResolveProductManagementService()
{ var repository = new SqlProductRepository(this.connectionString); var srvc = new ProductManagementService(repository, this.mapper); lock (this.syncRoot)
{ this.repositories.Add(srvc, repository);
} return srvc;
}

300
Метод начинается с преобразования всех зависимостей. Оно аналогично реализации из листинга 8-2. Но перед тем как вернуть разрешенный сервис, контейнер должен вспомнить связь между сервисом и репозиторием.
В WCF приложении присутствует только один экземпляр контейнера, и поскольку, скорее всего, он будет получать одновременные запросы, вам необходимо заблокировать словарь перед тем, как добавить в него репозиторий. Добавление элементов в словарь не является полностью безопасной операцией, поэтому вам необходимо выполнить блокировку, чтобы убедиться, что все репозитории сохранены для последующих даже внутренних одновременных вызовов.
Если вы обратитесь снова к листингу 7-7, вы заметите, что реализация
IInstanceProvider уже вызывает метод
Release для контейнера. До настоящего момента вы не реализовывали этот метод, полагаясь на то, что сборщик мусора выполнит эту работу, но для устраняемых зависимостей существенно, чтобы вы осознали эту возможность уничтожения. Ниже приведена эта реализация.
Листинг 8-5: Высвобождение устраняемых зависимостей
1.
public void Release(object instance)
2.
{
3.
var srvc = instance as IProductManagementService;
4.
if (srvc == null)
5.
{
6.
return;
7.
}
8.
lock (this.syncRoot)
9.
{
10.
SqlProductRepository repository;
11.
if (this.repositories.TryGetValue(srvc, out repository))
12.
{
13.
repository.Dispose();
14.
this.repositories.Remove(srvc);
15.
}
16.
}
17.
}
Строка 13: Уничтожает репозиторий
Строка 14: Удаляет репозиторий из словаря
Поскольку метод
Release принимает любые типы объектов, вам для начала потребуется
граничный операт ор (Guard Clause), чтобы убедиться, что instance является
IProductManagementService
Параллельные цепочки могут вызывать метод
Release одновременно, поэтому вы должны еще раз сериализовать обращение к словарю repositories
, чтобы убедиться в том, что параллельные цепочки не искажают состояние этого словаря. Если бы репозитории не были удалены из словаря, то это могло бы привести к утечке памяти.
Переменная srvc выступает в роли ключа к словарю, поэтому вы можете использовать ее для поиска устраняемой зависимости. Когда вы найдете такую зависимость, вы можете уничтожить ее и удалить из словаря, чтобы удостовериться в том, что контейнер не оставляет ее "живой".

301
Примеры, продемонстрированные в листингах 8-4 и 8-5, используются специально для работы с одной конкретной устраняемой зависимостью:
SqlProductRepository
. Довольно банально было бы, если бы мы расширили код для того, чтобы иметь возможность работать с зависимостями любого вида, но после этого ситуация усложнилась бы.
Представьте, что вам приходится работать с многочисленными устраняемыми зависимостями одного и того же объекта, или вложенными устраняемыми зависимостями, причем часть из них должны быть Singleton'ами, а некоторые должны быть Transient – и это мы еще даже не начинали обсуждать более сложные стили существования!
П одсказка
Окажите себе самому услугу и воспользуйтесь DI-контейнером вместо того, чтобы пытаться решить все эти проблемы в пользовательском коде. Единственная причина, почему я привожу этот пользовательский код, – объяснить принципы управления жизненным циклом.
DI-контейнеры могут работать со сложными сочетаниями стилей существования, и предлагают возможности (например, метод
Release
) для явного высвобождения компонентов после завершения работы с ними. Мы должны не забывать использовать эти методы для того, чтобы избежать утечки памяти, особенно в тех случаях, когда одна или более одной зависимости из сконфигурированных являются устраняемыми.
На данный момент мы обсудили в некоторых подробностях механизм управления жизненным циклом. Будучи потребителями, мы не можем управлять жизненным циклом внедренных зависимостей; эта ответственность ложится на плечи
Composer
, который может выбрать между двумя вариантами: разделить один экземпляр между многими потребителями или наделить каждого потребителя своим собственным приватным экземпляром. Эти Singleton и Transient стили существования являются всего лишь самыми универсальными представителями огромного набора стилей существования, и оставшуюся часть главы мы будем использовать для рассмотрения каталога стратегий жизненного цикла.

302 8.3. Каталог стилей существования объектов
Т еперь, когда в предыдущих разделах мы рассмотрели принципы, лежащие в основе механизма управления жизненным циклом, мы потратим оставшуюся часть главы на рассмотрение паттернов универсальных стилей существования.
П римечание
Я буду использовать аналогичные примеры в рамках всего раздела. Но для того чтобы позволить нам с вами сконцентрироваться на самых главных моментах, я буду составлять поверхностные иерархии и иногда игнорировать проблему, связанную с устраняемыми зависимостями, для того, чтобы избежать этой дополнительно й сложности.
Поскольку вы уже сталкивались и с Singleton, и с T ransient , мы начнем с этих стилей существования, а затем раскроем остальные типы. По мере того, как мы будем продвигаться по этим стилям существования, мы будем переходить от общепринятых стилей существования к более экзотичным, как это описано в таблице 8-2.
Таблица 8-2: Паттерны стилей существования, рассматриваемые в данном разделе
Название
Описание
Singleton
Один экземпляр постоянно повторно используется
T ransient
Всегда используются новые экземпляры
Per Graph
Один экземпляр повторно используется в пределах каждой диаграммы объектов
W eb Request
Context
В большинстве случаев для одного веб-запроса используется один экземпляр каждого типа
Pooled
Используются экземпляры из пула готовых объектов
Lazy
Зависимость, требующая больших затрат, создается и используется в замедленно м темпе
Future
Зависимость станет доступна в будущем
Несмотря на то, что вы можете использоват ь такие стили существования, как Pooled, довольно редко, хорошо бы о них все-таки знать, а этот список должен дать вам хорошее представление о широком ряде доступных стилей существования. По сравнению с более сложными стилями существования, Singleton может казаться более приземленным, но он, тем не менее, является универсальной и уместной стратегией жизненного цикла.
Singleton
В этой книге мы время от времени косвенным образом использовали стиль существования
Singleton. Его название одновременно является и достаточно понятным, и несколько запутанным. Оно имеет множество смыслов, поскольку результирующее поведение похоже на паттерн проектирования Singleton, но структура отличается.

303
П редупреждение
Не путайте стиль существования Singleton с паттерном проектирования Singleton.
В пределах области применения одного
Composer компонент со стилем существования
Singleton ведет себя приблизительно как Singleton. Всякий раз, когда потребитель выполняет запрос компонента, используется один и тот же экземпляр.
Но на этом схожесть заканчивается. Потребитель не может обратиться к зависимости с
Singleton областью применения посредством статического члена, и если мы просим два разных
Composer
'а передать нам экземпляр, то мы получим два разных экземпляра.
П одсказка
Используйте стиль существования Singleton всякий раз, когда это можно сделать.
Поскольку используется только один экземпляр, стиль существования Singleton в основном потребляет минимальное количество памяти. Единственный момент, когда так не происходит, – это когда экземпляр редко используется, но потребляет чрезмерное количество памяти. В таких случаях стиль существования Lazy с примыкающим экземпляром T ransient может стать наилучшей конфигурацией (но я долго пытался найти разумные примеры таких ситуаций).
Когда использовать Singl eton
При возможности используйте стиль существования Singleton. Главной проблемой, которая может не дать вам использовать Singleton, может стать то, что компонент является пот око-безопасным. Поскольку экземпляр Singleton используется совместно потенциально большим количеством потребителей, он должен уметь управлять одновременными обращениями.
Все сервисы, которые не сохраняют свое состояние, по определению являются потоко- безопасными, как и неизменные типы, и очевидные классы, специально созданные потоко-безопасными. В таких случаях нет причины не делать их Singleton'ами.
Вдобавок к аргументу об эффективности, некоторые зависимости могут работать так, как это было задумано, только если они используются кем-то совместно. Например, это касается реализаций паттерна проектирования Circuit Breaker, а также кэшей оперативной памяти. В этих случаях то, что реализации являются потоко-безопасными, как раз-таки существенно.
Давайте поближе рассмотрим репозиторий, находящийся в оперативной памяти.
П ример: Использование потоко-безопасного репозитория, находящегося в оперативной памяти
Давайте еще раз обратим наше внимание на реализацию
ICommerceServiceContainer таким образом, как это описано в разделах "Пример: подключение сервиса управления продуктами", "Управление жизненным циклом с помощью контейнера" и "Управление устраняемыми зависимостями". Вместо использования
ProductRepository
, базирующегося на SQL Server, мы могли бы решить, использовать потоко-безопасную реализацию в оперативной памяти. Для того чтобы хранилище данных, находящееся в

304 оперативной памяти, имело какой-либо смысл, оно должно совместно использоваться всеми запросами, поэтому оно должно быть потоко-безопасным, как это проиллюстрировано на рисунке 8-10.
Рисунок 8-10: Когда составные экземпляр ы
ProductManagementService
, идущие отдельными потоками, обращаются к совместно используемому ресурсу, например, находящемуся в оперативной памяти
ProductRepository
, мы должны убедиться, что совместно используемый ресурс является потоко-безопасным.
Вместо явной реализации такого репозитория, как Singleton, мы можем использовать конкретный класс и соответствующим образом его ограничить. Единственное требование
– он должен быть потоко-безопасным.
Листинг 8-6 демонстрирует, как контейнер может возвращать новые экземпляры всякий раз, когда его просят разрешить
IProductManagementService
, в то время, как
ProductRepository используется всеми экземплярами совместно.
Листинг 8-6: Управление Singleton'ами
1.
public class SingletonContainer : ICommerceServiceContainer
2.
{
3.
private readonly ProductRepository repository;
4.
private readonly IContractMapper mapper;
5.
public SingletonContainer()
6.
{
7.
this.repository =
8.
new InMemoryProductRepository();
9.
this.mapper = new ContractMapper();
10.
}
11.
public IProductManagementService
12.
ResolveProductManagementService()
13.
{
14.
return new ProductManagementService(
15.
this.repository, this.mapper);
16.
}
17.
public void Release(object instance) { }
18.
}

305
Строка 3-4: Экземпляр ы Singleton
Строка 7-9: Создает Singleton'ы
Строка 14-15: Создает сервис
Строка 17: Ничего не делает
Жизненный цикл Singleton довольно легко реализовать: вы храните ссылку на каждую зависимость на протяжении всего жизненного цикла контейнера. Обратите внимание на то, что вы используете ключевое слово readonly
, чтобы убедиться, что вы не можете случайно изменить ссылки поздней датой. Строгой необходимости в этом для реализации стиля существования Singleton нет, но это обеспечивает некоторую дополнительну ю безопасность, цена которой – написание восьми букв.
Всякий раз, когда контейнер просят разрешить экземпляр
IProductManagementService
, он создает T ransient экземпляр с внедренными в него Singleton'ами. В данном примере и repository
, и mapper являются Singleton'ами, но при желании вы можете смешивать стили существования.
Стиль существования Singleton – один из самых простых для реализации стилей. Все, что для него нужно, – хранить ссылку на объект и использовать этот же объект каждый раз, когда его запрашивают. Экземпляр не выходит за рамки области применения до тех пор, пока не выйдет за рамки области применения
Composer
. Когда это происходит,
Composer должен избавиться от объекта, если он принадлежит к устраняемому типу.
Еще одним простым для реализации стилем существования является стиль Transient.
Transient
Стиль существования Transient включает в себя возврат нового экземпляра всякий раз, когда этот экземпляр запрашивается. До тех пор пока возвращаемый экземпляр не реализует
IDisposable
, следить не за чем. Наоборот, когда экземпляр реализует
IDisposable
,
Composer должен иметь его ввиду и явным образом избавляться от него, когда его просят высвободить подходящую диаграмму объектов.
Стоит заметить, что в настольных и простых приложениях мы стремимся разрешать полноценную иерархию объектов всего единожды: при запуске приложения. Это означает, даже для Transient компонентов, что будет создано только несколько экземпляров, и что они могут существовать длительно е время. В худшем случае, когда на одну зависимость приходится только один потребитель, конечный результат разрешения диаграммы истинных T ransient компонентов эквивалентен разрешению диаграммы истинных
Singleton'ов или любого их сочетания. Это все потому, что диаграмма разрешается всего лишь раз, поэтому разницы в поведении никогда не возникает.
Когда использовать Tran sie nt
Стиль существования Transient – самый безопасный из всех стилей существования, но в то же время и один из самых не эффективных, поскольку он может привести к тому, что будет создано и уничтожено сборщиком мусора бесчисленное множество экземпляров, в то время как одного экземпляра было бы достаточно. Но если вы сомневаетесь насчет

306 потоковой безопасности компонента, стиль существования T ransient безопасен, поскольку каждый потребитель имеет свой собственный экземпляр зависимости.
В большинстве случаев мы можем безопасно менять стиль существования Transient на такой контекстно-ограниченн ый стиль существования, как W eb Request Context, в котором доступ к зависимости также гарантированно сериализируется, но это зависит от среды выполнения (использование W eb Request Context в настольных приложениях не имеет смысла).
П ример: разрешение разнообразны х репозиториев
Ранее в этой главе вы видели несколько примеров использования стиля существования
T ransient . В листинге 8-2 repository создается и внедряется в методе, выполняюще м разрешение, а контейнер не содержит ссылок на него. Затем в листингах 8-4 и 8-5 вы увидели, как работать с устраняемым T ransient компонентом.
В этих примерах вы могли заметить, что mapper повсюду остается Singleton'ом. Это истинный, не сохраняющий свое состояние сервис, поэтому нет необходимости создавать новый экземпляр для каждого вновь созданного
ProductManagementService
. Что действительно заслуживает внимания, так этот тот факт, что вы можете сочетать зависимости с различными стилями существования.
Когда разнообразным компонентам нужна одна и та же зависимость, каждый из этих компонентов получает отдельный экземпляр этой зависимости. Следующий листинг демонстрирует метод, который выполняет разрешение ASP.NET MVC контроллера.
Листинг 8-7: Разрешение Transient
DiscountRepositorys
1.
public IController ResolveHomeController()
2.
{
3.
var connStr = ConfigurationManager
4.
.ConnectionStrings["CommerceObjectContext"]
5.
.ConnectionString;
6.
var discountCampaign =
7.
new DiscountCampaign(
8.
new SqlDiscountRepository(connStr));
9.
var discountPolicy =
10.
new RepositoryBasketDiscountPolicy(
11.
new SqlDiscountRepository(connStr));
12.
return new HomeController(
13.
discountCampaign, discountPolicy);
14.
}
Строка 8: Новый экземпляр
SqlDiscountRepository
Строка 11: Еще один экземпляр
SqlDiscountRepository
Как для класса
DiscountCampaign
, так и для класса
RepositoryBasketDiscountPolicy нужна зависимость
DiscountRepository
. В случае, когда
DiscountRepository является
T ransient , каждый потребитель получает свой собственный приватный экземпляр, поэтому
DiscountCampaign получает один экземпляр, а
RepositoryBasketDiscountPolicy
– другой.
Стиль существования Transient означает, что каждый потребитель получает приватный экземпляр зависимости даже в тех случаях, когда разнообразные потребители в одной и

307 той же диаграмме объектов обладают одной и той же зависимостью (как в случае с листингом 8-7). Если множество потребителей совместно используют одну и ту же зависимость, то данный подход будет неэффективным, но если реализация является потоко-безопасной, то в этом случае наиболее эффективный стиль существования
Singleton не подходит. В таких ситуациях может больше подойти стиль существования Per
Graph.
Per Graph
Singleton – это наиболее эффективный стиль существования, а T ransient – самый безопасный, но можем ли мы разработать такой стиль существования, который сочетал бы в себе преимущества этих двух стилей? Несмотря на то, что мы не можем получить самое лучшее от этих двух стилей, в некоторых случаях имеет смысл распределить единичный экземпляр по всей единичной разрешенной диаграмме. Мы можем рассматривать это, как некоторого рода локально-огранич енный Singleton. Мы можем использовать общий экземпляр в рамках единичной диаграммы объектов, но не разделяем этот экземпляр с другими диаграммами.
Каждый раз при разрешении диаграммы объектов мы создаем только один экземпляр каждой зависимости. Если эта зависимость используется несколькими потребителями, они разделяют между собой один и тот же экземпляр; но при разрешении новой диаграммы объектов мы создаем новый экземпляр.
Когда использовать Per Graph
В большинстве случаев мы можем использовать стиль существования Per Graph там, где могли бы использовать и T ransient. Обычно мы допускаем, что поток, разрешающий диаграмму объектов, также является единственным потребителем этой диаграммы объектов. Даже когда рассматриваемая зависимость не является потоко-безопасной, мы можем использовать стиль существования Per Graph, поскольку общий экземпляр совместно используется только потребителями, которые проходят по одному и тому же потоку.
В редких случаях, когда один или более одного потребителя проносятся по новым потокам и используют зависимость из этих потоков, T ransient все еще остается самым безопасным стилем существования, но такое случается редко. Могут встречаться и такие ситуации, когда зависимость представляет собой изменчивый ресурс, а для каждого потребителя необходимо его собственное приватное состояние. В этом случае Transient будет корректным жизненным циклом, поскольку он дает гарантию того, что экземпляры никогда не будут использоваться коллективно.
По сравнению с Transient, при использовании Per Graph не возникает никаких дополнительных издержек, поэтому мы часто можем использовать его в качестве замены
T ransient . Несмотря на то, что издержки отсутствуют, нам также не гарантировано и какое-либо преимущество. Мы получаем только некоторое усиление эффективности в случае, если диаграмма единичного объекта содержит многочисленных потребителей одной и той же зависимости. В этом случае мы можем разделить экземпляр между этими потребителями; но если совместно используемые зависимости отсутствуют, то будет нечего делить, и поэтому не будет никаких преимуществ.

308
П римечание
В большинстве случаев Per Graph лучше Transient, но не многие DI-контейнеры его поддерживают.
В случаях, когда реализация является потоко-безопасной, наиболее эффективным вариантом все еще остается стиль существования Singleton.
П ример: Совме стное использование Repository в рамках диаграммы
В листинге 8-7 вы видели, как каждый потребитель получал свой собственный приватный экземпляр
SqlDiscountRepository
. Этот класс не является потоко-безопасным, поэтому вам не следует конфигурировать его в виде Singleton. Но вы не рассчитываете на то, что разнообразные потоки будут обращаться к индивидуальным экземплярам
HomeController
, поэтому разделение экземпляра
SqlDiscountRepository между двумя потребителями будет безопасным. Следующий листинг демонстрирует, как создать единичный экземпляр Per Graph в методе
ResolveHomeController
Листинг 8-8: Разрешение единичного per graph repository
1.
public IController ResolveHomeController()
2.
{
3.
var connStr = ConfigurationManager
4.
.ConnectionStrings["CommerceObjectContext"]
5.
.ConnectionString;
6.
var repository =
7.
new SqlDiscountRepository(connStr);
8.
var discountCampaign =
9.
new DiscountCampaign(repository);
10.
var discountPolicy =
11.
new RepositoryBasketDiscountPolicy(repository);
12.
return new HomeController(discountCampaign, discountPolicy);
13.
}
Строка 6-7: Совместно используемый экземпляр
SqlDiscountRepository
Строка 8-11: Внедрение совместно используемого экземпляра
Вместо того чтобы создавать отдельные экземпляр ы для всех потребителей, вы создаете один экземпляр, который можете разделить между всеми потребителями. Вы внедряете этот единственный экземпляр как в
DiscountCampaign
, так и в
RepositoryBasketDiscountPolicy
. Обратите внимание на то, что по сравнению с
Singleton'ами, в которых совместно используемый экземпляр является приватным членом контейнера, экземпляр repository является локальным по отношению к методу
ResolveHomeController
; при следующем вызове метода будет создан новый экземпляр, а затем он будет разделен между двумя потребителями.
Стиль существования Per Graph является хорошей альтернативой стилю T ransient в тех случаях, когда единственной причиной отказа от использования Singleton является тот факт, что реализация не потоко-безопасна. Несмотря на то, что Per Graph предлагает в целом удобное решение для разделения зависимостей в рамках четко определенных границ, существуют другие, более специализиро ванные альтернативы.

309
Web Request Context
Как пользователям приложения нам хотелось бы получать ответ от этого приложения как можно быстрее, даже в тех случаях, когда в это же самое время оно используется и другими пользователями. Нам бы не хотелось, чтобы наш запрос был поставлен в очередь наряду с запросами всех остальных пользователей. Возможно, нам пришлось бы чрезмерно долго ждать ответа, если бы перед нашим запросом было бы много других запросов.
Чтобы решить эту проблему, веб-приложения управляют запросами одновременно. .NET инфраструктура защищает нас от этого, позволяя каждому запросу выполняться в своем собственном контексте и со своим собственным экземпляром
Controller
(если вы используете ASP.NET MVC) или
Page
(если вы используете ASP.NET Web Form s).
В связи с параллельным выполнением зависимости, которые не являются потоко- безопасными, не могут использоваться как Singleton'ы. С другой стороны, использование их в виде T ransient может быть неээфективным или даже совершенно проблематичным в тех случаях, если нам нужно разделить зависимость между различными потребителями в рамках одного и того же запроса.
Несмотря на то, что ASP.NET движок не гарантирует, что один запрос будет выполняться исключительно в одном потоке, он гарантирует, что код выполняется последовательно.
Это означает, что если мы хотим разделить зависимость только в рамках одного запроса, то потоко-безопасность в данном случае не является проблемой.
Рисунок 8-11 демонстрирует, как работает стиль существования W eb Request Context.
Зависимости ведут себя как Singleton'ы в пределах одного запроса, но не разделены между запросами. Каждый запрос имеет свой собственный набор связанных зависимостей.
Рисунок 8-11: Стиль существования Web Request Context указывает на то, что мы создаем не более одного экземпляра на один запрос. Экземпляр
DiscountRepository совместно используется
BasketDiscountPolicy и
DiscountCampaign
, но только в рамках запроса 1.
Запрос 2 использует ту же самую конфигурацию, но экземпляр ы ограничены этим запросом.
Любые устраняемые компоненты должны быть уничтожены после окончания запроса.
Когда использовать Web Request Conte xt
Очевидно, что стиль существования W eb Request Context имеет смысл только в веб- приложении. Даже в пределах веб-приложения он может быть использован только в

310 запросах. Несмотря на то, что запросы составляют большую часть веб-приложения, стоит отметить, что если мы проносимся по исходному потоку с целью асинхронной обработки, то этот стиль существования не применим, поскольку исходный поток не будет синхронизироваться с веб-запросом.
Стиль существования Web Request Cont ext предпочтительнее Transient, но стиль существования Singleton все еще более эффективен. Используйте W eb Request Context только в тех ситуациях, в которых Singleton не работает.
П римечание
Если вы следуете общему совету разрешать только одну диаграмму объектов для одного веб-запроса, то стили существования Web Request Context и Per Graph функционально эквивалентны.
П одсказка
Если вам когда-нибудь понадобится сформировать в веб-запросе Ent ity Framework
ObjectContext
, то самый лучший для этого стиль существования – Web Request Cont ext.
Экземпляры
ObjectContext не являются потоко-безопасными, но в данном случае на один веб-запрос должен быть только один
ObjectContext
Не все DI-контейнеры поддерживают данный стиль существования, поэтому очевидно мы можем использовать его только, если он доступен.
П одсказка
Некоторые DI-контейнеры позволяют вам создавать свои собственные расширения стилей существования, поэтому, возможно, это вариант в тех случаях, если выбранный вами контейнер не поддерживает стиль существования Web Request Context. К тому же это может быть не тривиальной затеей.
Что касается других стилей существования, то мы можем сочетать стили существования, например, таким образом, что некоторые из них конфигурируются как Singleton'ы, а другие совместно используются в рамках веб-запросов.
П ример: компоновка HomeControl ler с Repository, разделенным между веб- запросами
В данном примере вы увидите, как компоновать ASP.NET MVC экземпляр
HomeController с зависимостями, если для двух этих зависимостей необходим
DiscountRepository
. Эта ситуация обрисована на рисунке 8-11: для
HomeController нужны
BasketDiscountPolicy и
DiscountCampaign
, а для этих двух зависимостей, в свою очередь, нужен
DiscountRepository
П римечание
Шаблонный код данного раздела сложнее того, что гарантируется одноразовым решением. Я не думаю, что вам когда-нибудь придется писать пользовательский код

311 жизненного цикла Web Request Cont ext, подобный данному, но мне хочется показать вам, как он работает. Вместо написания такого кода используйте DI-контейнер, который поддерживает этот стиль существования.
Вам хотелось бы использоват ь совместный
SqlDiscountRepository
, но так как этот класс не является потоко-безопасным, вы не можете коллективно использовать его в качестве
Singleton. Вместо этого вы будете использовать его совместно в рамках каждого веб- запроса. Специализированный контейнер компонует экземпляры
HomeController так, как это продемонстрировано в следующем листинге.
Листинг 8-9: Компоновка
HomeController
1.
public IController ResolveHomeController()
2.
{
3.
var discountPolicy =
4.
new RepositoryBasketDiscountPolicy(
5.
this.ResolveDiscountRepository());
6.
var campaign = new DiscountCampaign(
7.
this.ResolveDiscountRepository());
8.
return new HomeController(
9.
campaign, discountPolicy);
10.
}
Строка 5,7: Делегирует резолюцию репозитория
Строка 8-9: Возвращает сформированный
HomeController
На данный момент с большинством операторов этого метода вы должны быть уже знакомы. Единственное, что стоит отметить, – это то, что вы делегируете резолюцию
DiscountRepository отдельному методу. Данный метод гарантирует, что на один веб- запрос разрешается хотя бы один экземпляр.
При запросе разрешения
DiscountRepository контейнер должен проверить, существует ли уже экземпляр, связанный с веб-запросом. Если он существует, то возвращается данный экземпляр; иначе экземпляр создается и связывается с веб-запросом перед тем, как будет возвращен. Как показывает следующий листинг, в ASP.NET (как в M VC, так и в
W eb Forms) вы можете использовать текущий
HttpContext для поддержания этой связи в работоспособном состоянии.
Листинг 8-10: Разрешение зависимости, ограниченной контекстом веб-запроса
1.
protected virtual DiscountRepository ResolveDiscountRepository()
2.
{
3.
var repository = HttpContext.Current
4.
.Items["DiscountRepository"]
5.
as DiscountRepository;
6.
if (repository == null)
7.
{
8.
var connStr = ConfigurationManager
9.
.ConnectionStrings["CommerceObjectContext"]
10.
.ConnectionString;
11.
repository = new SqlDiscountRepository(connStr);
12.
HttpContext.Current
13.
.Items["DiscountRepository"] = repository;
14.
}
15.
return repository;
16.
}

312
Строка 3-5: Выполняет поиск репозитория в контексте запроса
Строка 12-13: Сохраняет репозиторий в контексте запроса
Особенностью стиля существования Web Request Cont ext является повторное использование экземпляров уже связанных с текущим запросом, поэтому первое, что необходимо сделать, – проверить, существует ли уже необходимый экземпляр. Если он существует, то вы можете его вернуть. Если экземпляр не найден, то вы должны создать его и связать с текущим веб-запросом перед тем, как вернуть этот экземпляр.
При первом вызове метода
ResolveDiscountRepository он создает репозиторий и связывает его с запросом таким образом, чтобы каждый последующий вызов повторно использовал один и тот же экземпляр.
При завершении запроса вы могли бы оставить устраняемую зависимость в веб-запросе, что привело бы к утечке памяти, поэтому при завершении запроса вы должны убедиться еще и в том, что все зависимости высвобождены. Один из способов осуществления этого – зарегистрировать пользовательский
IHttpModule
, который присоединяется к событию
EndRequest для того, чтобы надлежащим образом уничтожить все устраняемые зависимости. В следующем листинге продемонстрирован пример реализации.
Листинг 8-11: Высвобождение устраняемых зависимостей, ограниченных контекстом веб-запроса
1.
public class DiscountRepositoryLifestyleModule : IHttpModule
2.
{
3.
public void Init(HttpApplication context)
4.
{
5.
context.EndRequest += this.OnEndRequest;
6.
}
7.
public void Dispose() { }
8.
private void OnEndRequest(object sender, EventArgs e)
9.
{
10.
var repository = HttpContext.Current
11.
.Items["DiscountRepository"];
12.
if (repository == null)
13.
{
14.
return;
15.
}
16.
var disposable = repository as IDisposable;
17.
if (disposable != null)
18.
{
19.
disposable.Dispose();
20.
}
21.
HttpContext.Current
22.
.Items.Remove("DiscountRepository");
23.
}
24.
}
Строка 10-11: Выполняет поиск репозитория в контексте запроса
Строка 16-20: Уничтожает репозиторий
Строка 21-22: Удаляет репозиторий из контекста запроса
При завершении веб-запроса вы предпринимаете попытку поиска репозитория в контексте запроса. При обнаружении репозитория вы можете уничтожить его, если это можно

313 сделать. Независимо от того, является ли этот репозиторий устраняемым или нет, вы должны не забыть удалить его из контекста запроса.
Стиль существования Web Request Cont ext связывает зависимость с текущим запросом, сохраняя и извлекая этот запрос посредством
HttpContext.Current
. Данный пример продемонстрировал специализированное решение, но методика может быть обобщена таким образом, чтобы произвольное количество зависимостей множества различных типов можно было связывать с контекстом запроса. Это относится к сфере соответствующего DI-контейнера.
Вариация: Session Request C ontext
Редко встречающейся вариацией стиля существования Web Request Context является стиль существования, при котором область применения жизненного цикла зависимостей связана не с конкретным запросом, а с сессией. Это намного более экзотичный стиль существования, и если вы решите его использовать, то вам следует делать это с чрезвычайной осторожностью.
С технической точки зрения может показаться, что он похож на Web Request Cont ext, но наиболее важное отличие заключается в том, что, несмотря на то, что HTTP-запрос имеет вполне определенный жизненный цикл, сессии не обладают таким определенным жизненным циклом. Сессия в редких случаях заканчивается явным образом, а чаще всего завершается после некоторого периода бездействия. Это означает, что все зависимости, зарегистрированные данным образом, вероятнее всего, длительное время сохраняются там, где они не используются. Все это время они занимают память, что может сильно повлиять на производительность приложения.
П редупреждение
Используйте стиль существования Session Request Context только, если он вам действительно необходим. Вероятнее всего, использование данного стиля понизит производительно сть вашей системы.
П одсказка
Если вам нужно привязать некие зависимости к сессии, то вам лучше сконфигурировать их с помощью Web Request Context и посредством фабрики, которая присоединяет каждый экземпляр на основании соответствующего ключа сессии. Данный подход позволяет вам более явным образом управлять жизненным циклом зависимости, несмотря на то, что она все еще связана с сессией.
Еще одна проблема, с которой мы сталкиваемся, – это то, что состояние сессии может быть сохранено во внешнем хранилище, например на отдельном сервере сессий или на
SQL Server session state
. При таких конфигурациях должны быть сериализованы все данные сессии, а также зависимости, на которые оказывается влияние. Сериализация типа может быть так же проста, как и наделение этого типа атрибутом
[Serializable]
, но существует еще кое-что, что мы должны не забыть сделать.
В целом, я считаю Session Request Context непривлекательным и не припомню, чтобы когда-либо видел, как он используется.

314
Вариация: Thread C ontext
Еще одна, более привлекательная вариация стиля существования Web Request Context, – это ассоциирование зависимости с конкретным потоком. Сущность остается той же: в каждом потоке управление зависимостью ведется как с Singleton, но для каждого потока существует свой экземпляр зависимости.
Данный подход наиболее полезен в сценариях, при которых мы проносимся по многочисленным эквивалентным рабочим потокам и используем начало каждого потока в качестве Com position Root. Эта ситуация проиллюстрирована на рисунке 8-12.
Рисунок 8-12: Когда приложение незамедлительно проносится по некоторому количеству параллельных задач и разрешает зависимости в рамках каждого потока, мы можем использовать стиль существования Thread Context для того, чтобы убедиться, что любые зависимости, не являющиеся потоко-безопасными, могут быть разделены между любым количеством потребителей одного и того же потока. Каждый поток имеет свои собственные экземпляры.
Чтобы реализовать стиль существования T hread Cont ext , мы можем поискать запрашиваему ю зависимость в Thread Local Storage (TLS). Если мы ее там обнаружим, то можем повторно ее использовать; иначе, мы создаем эту зависимость и сохраняем ее в
TLS.
Между тем, как Session Request Context может быть совершенно опасным, а T hread
Context – слегка экзотичным, стиль существования Web Request Context полезен. Он позволяет нам разделять зависимости в пределах веб-запроса, не беспокоясь при этом о том, являются ли они потоко-безопасными. W eb Request Context – это нечто среднее между Singleton и T ransient.
Web Request Cont ext является более эффективной альтернативой стиля существования
Transient, но мы можем использовать его только в веб-приложениях. Если мы имеем зависимости, для управления которыми требуются большие затраты, в других типах приложений, то мы можем обратиться к другим возможностям оптимизации.
Pooled
Иногда для создания компонентов необходимы большие затраты. Универсально е решение этой проблемы – возможность легкого доступа к пулу уже созданных компонентов.
Хорошо известный пример – подключения баз данных, которые практически всегда находятся в пуле. Мы автоматически применяем организацию пулов подключений баз данных, и можем использовать ту же самую технологию, если у нас есть пользовательские компоненты, создание которых требует больших затрат.

315
Несмотря на то, что общая концепция объектов в пуле должна быть вам знакома, в таблице 8-3 перечислены некоторые вариации реализации.
Таблица 8-3: Варианты реализации пулов объектов
Вариант
Описание
Подготовка пула

1   ...   18   19   20   21   22   23   24   25   ...   43


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