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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница19 из 43
1   ...   15   16   17   18   19   20   21   22   ...   43
реализат ору (implementer). Т акой подход эффективно преобразует
Page в humble-объект ("скромный" объект), раскрывая другие члены, такие, как обработчики события нажатия кнопки только для того, чтобы делегировать полномочия реализатору преобразованного
Page
Рисунок 7-14: В ASP.NET мы можем использовать точку входа в приложение
(global.asax) для того, чтобы конфигурировать зависимости, но потом до того, как мы сможем продолжить композицию, нам придется ждать, пока фреймворк не создаст новый объект
Page
. В пределах каждого
Page мы можем использовать сконфигурированные зависимости для того, чтобы скомпоновать реализатор, который реализует все поведение класса
Page
Различие между вариантом перемещения Com position Root в каждый класс и вариантом использования Service Locator – трудно уловимо. Разница заключается в том, что благодаря Service Locator мы можем преобразовывать зависимости каждого класса
Page в индивидуальном порядке, и использовать их напрямую в пределах класса
Page
. Как обычно бывает с Service Locator, он склонен к размытию фокуса класса. Кроме того, довольно заманчиво сохранить контейнер и использовать его для того, чтобы преобразовать остальные зависимости как надо.
Чтобы противодействовать этой тенденции, важно использовать наш контейнер только для преобразования реализатора, а затем – забыть о нем. Это позволяет нам руководствоваться подходящими DI-паттернами (например, Constructor Injection) для остальной части кода приложения.

267
Несмотря на то, что это только теория, вы расслабитесь, услышав, что это легко реализовать. Лучше всего это иллюстрируется на примере.
Пример: подключение CampaignPresenter
Шаблонное приложение Com merce, которое вы знаете и любите, поддерживает такие возможности, как скидки на т овары и список реком ендуемых товаров, но до настоящего момента вы не обеспечивали потребителей приложением, которое управляло бы этими аспектами. В данном примере мы рассмотрим, как компоновать ASP.NET приложение
(продемонстрировано на рисунке 7-15), которое позволяет потребителю обновлять данные об акциях для товара.
Рисунок 7-15: Приложение
CampaignManagement позволяет промышленным потребителям редактировать данные об акциях (Featured (Рекомендуемые) и Discount Price (Цена со скидкой)) для товара. Это ASP.NET приложение, построенное с элементом управления
GridView
, привязанным к элементу управления
ObjectDataSource
Говоря простыми словами, это приложение состоит из единственного элемента управления, привязанного к
ObjectDataSource
. Источник данных – это класс конкретного приложения, который делегирует свое поведение доменной модели и, в конечном счете, передает его в библиотеку доступа к данным, которая хранит данные в базе данных SQL
Server.
Вы все еще можете использовать global.asax для конфигурирования зависимостей, но вы должны отложить компоновку приложения до тех пор, пока не будут созданы
Page и его
ObjectDataSource
. Конфигурирование зависимостей схоже с предыдущими примерами.
Конфигурирова ние зависимосте й в ASP.NET
В ASP.NET точкой входа в приложение является файл global.asax, и, несмотря на то, что вы ничего не можете компоновать в этой точке, вы можете создать свой mise en place, подготавливая все для запуска приложения: protected void Application_Start(object sender, EventArgs e)
{ this.Application["container"] = new CampaignContainer();
}

268
Единственное, что вы здесь делаете, – создаете ваш контейнер и сохраняете его в
Application Cont ext , поэтому вы можете использовать его, когда вам это нужно. Это позволяет вам совместно использовать контейнер в рамках отдельных веб-запросов, которые являются существенными, если вам нужно сохранить некоторые зависимости на время жизненного цикла процесса (более подробно о жизненных циклах мы поговорим в главе 8).
П римечание
Как и во всех остальных примерах данной главы, я использую Poor Man's DI для того, чтобы продемонстрировать основные рассматриваемые принципы.
CampaignContainer
– это пользовательский класс, созданный специально для этого примера, но вы можете легко заменить его выбранным вами DI-контейнером.
Большинство различных
Page и объектов источников данных могут совместно использовать один и тот же контейнер посредством обращения к Application Context. Тем не менее, этот подход несет за собой опасность неправильного использования его в качестве Service Locator, поскольку любой класс может потенциально получить доступ к
Application Cont ext . Таким образом, важно делегировать реализацию классам, которые не могут получить доступ к Application Context. На практике это означает делегирование полномочий классам, реализованным в других отдельных библиотеках, которые не ссылаются на ASP.NET .
П римечание
Мы можем также продолжить свой путь слегка дисциплиниро ванно, сдерживая себя от обращения к Application Cont ext, за исключением реализации Composition Root . Это может хорошо подходить в тех случаях, когда все разработчики имеют опыт в написании слабо связанного кода; но если мы думаем, что некоторые члены команды могут не до конца понимать рассматриваемую проблему, мы можем удачно защитить код посредством использования отдельных библиотек. Раздел 6.5 описывает, как это сделать.
В текущем примере вы будете делегировать всю реализацию отдельной библиотеке логики отображения для того, чтобы убедиться, что никакие классы не обращаются напрямую к Application Context. Вы не позволяете библиотеке ссылаться на какую-либо сборку ASP.NET (например, System.Web).
Рисунок 7-16 демонстрирует частичное представление архитектуры приложения.
Основной момент – это тот факт, что вы используете классы центральной части приложения (
Default Page и
CampaignDataSource
) в качестве Composition Root'ов, которые преобразуют классы уровня "Логика представления" совместно с их зависимостями.

269
Рисунок 7-16: Центральная часть приложения
CampaignManagement
– это единственная составляющая приложения, ссылающаяся на ASP.NET. Класс
CampaignDataSource имеет конструктор по умолчанию, но действует как Composition Root или humble-объект, который делегирует все вызовы метода
CampaignPresenter
. Обычно стрелки обозначают указатели, а центральная часть приложения ссылается на все остальные модули приложения, поскольку она соединяет их вместе. Как модуль логики представления, так и модуль доступа к данным ссылаются на библиотеку доменной модели. Не все рассматриваемые классы продемонстрированы на этом рисунке.
Вооруженные знанием диаграммы зависимостей приложения, мы теперь можем реализовать Com position Root для кадра, продемонстрированного на рисунке 7-15.
Компоновка O bjectDataSource
Default Page
, продемонстрированный на рисунке 7-15, состоит из элемента управления
GridView и связанного с ним элемента управления
ObjectDataSource
. Как и в случае с классами
Page
, класс, используемый для
ObjectDataSource также должен обладать конструктором по умолчанию. Для достижения этой цели вы специально создаете класс, продемонстрированный в следующем листинге.

270
Листинг 7-12: Компоновка
Presenter в качестве источника данных
1.
public class CampaignDataSource
2.
{
3.
private readonly CampaignPresenter presenter;
4.
public CampaignDataSource()
5.
{
6.
var container =
7.
(CampaignContainer)HttpContext.Current
8.
.Application["container"];
9.
this.presenter = container.ResolvePresenter();
10.
}
11.
public IEnumerable SelectAll()
12.
{
13.
return this.presenter.SelectAll();
14.
}
15.
public void Update(CampaignItemPresenter item)
16.
{
17.
this.presenter.Update(item);
18.
}
19.
}
Строка 9: Формирует
Presenter
Строка 13, 17: Делегирует полномочия
Presenter
Класс
CampaignDataSource имеет конструктор по умолчанию, поскольку того требует
ASP.NET . Близкий по духу принципу Fail Fast (принцип быстрого отказа), он незамедлительно пытается извлечь контейнер из Application Cont ext и преобразовать экземпляр
CampaignPresenter
, который будет выступать в роли реальной реализации.
Все члены класса
CampaignDataSource делегируют вызов преобразованному предъявителю, таким образом, действуя как hum ble-объект.
П римечание
Для приверженцев паттернов проектирования класс
CampaignDataSource очень похож либо на Decorator, либо на Adapter. Он не реализует строго типизированный интерфейс, но обертывает соответствующую реализацию в класс, который соответствует требованиям, предъявляемых ASP.NET.
Вам может быть интересно, что мы приобретаем благодаря этому дополнительному уровню преобразования. Если вы привыкли к разработке через тестирование, то это должно быть вам понятно:
HttpContext.Current недоступен во время модульного тестирования, поэтому вы не можете выполнить модульное тестирование
CampaignDataSource
. Это важная причина того, почему вы должны сохранять его hum ble- объектом.
Несмотря на то, что эта конструкция, в лучшем случае, неудобна, она позволяет вам руководствоват ься соответствующими DI-паттернами из класса
CampaignPresenter и в дальнейшем проходить уровни приложения один за одним.

271
Компоновка Presenter
Я не буду знакомить вас с подробной информацией о
CampaignPresenter
, но стоит рассмотреть сигнатуру его конструктора, поскольку он использует Constructor Injection: public CampaignPresenter(CampaignRepository repository,
IPresentationMapper mapper)
Зависимостями
CampaignPresenter являются абстрактный класс
CampaignRepository и интерфейс
IPresentationMapper
. Как раз то, что делают эти абстракции, менее важно, чем то, как вы их компонуете. Это является задачей
CampaignContainer из следующего листинга. Вы можете вспомнить, что вы конфигурировали его в global.asax и регистрировали в Application Context .
Листинг 7-13: Преобразование
CampaignPresenter
1.
public CampaignPresenter ResolvePresenter()
2.
{
3.
string connectionString =
4.
ConfigurationManager.ConnectionStrings
5.
["CommerceObjectContext"].ConnectionString;
6.
CampaignRepository repository =
7.
new SqlCampaignRepository(connectionString);
8.
IPresentationMapper mapper =
9.
new PresentationMapper();
10.
return new CampaignPresenter(repository, mapper);
11.
}
Строка 6-7: Создает репозиторий
Строка 8-9: Создает преобразователь
Строка 10: Формирует
Presenter
Ответственност ь метода
ResolvePresenter
– скомпоновать экземпляр
CampaignPresenter
. Из конструктора вы знаете, что для него нужен
CampaignRepository
, поэтому вы преобразовываете его в экземпляр
SqlCampaignRepository
. Другой зависимостью является
IPresentationMapper
, и вы преобразуете ее в конкретный класс
PresentationMapper
Вооружившись всеми необходимыми зависимостями, вы можете впоследствии вернуть новый экземпляр
CampaignPresenter
Использование механизма внедрения зависимостей в рамках ASP.NET невозможно.
Главным недостатком использования каждого
Page и источника данных объекта в виде объединенного Com position Root и humble-объекта является то, что для этого необходимо дублирование большинства членов класса.
Обратили ли вы внимание на то, как каждый член
CampaignDataSource делегирует свою реализацию схожему по названию методу
CampaignPresenter
? Вам придется повторять эту идиому кода на протяжении всего ASP.NET приложения. Для каждого обработчика события нажатия кнопки вам необходимо определить и поддерживать в работоспособном состоянии связанный метод класса
Presenter и тому подобное.

272
Как мы уже обсуждали в главе 3, я приравниваю понятие Com position Root к такому понятию Бережливой разработки программ ного обеспечения (Lean Soft ware
Developm ent ), как последний от ветственный момент . В рамках таких фреймворков, как
ASP.NET MVC и W CF, мы можем отложить композицию приложения вплоть до точки входа в приложение, но в ASP.NET так не делается. Не важно, как упорно мы стараемся, мы можем отложить только принятие решений о композиции объектов, пока не столкнемся с требованием о необходимости наличия конструктора по умолчанию.
Потом это становится "самым возможным местом", в котором мы можем компоновать объекты. Несмотря на то, что мы считаем, что пришли к компромиссу, мы все еще следуем всеобщему духу Com position Root. Мы компонуем иерархии объектов настолько близко к верхним уровням приложения, насколько это возможно, и разрешаем корректные
DI-паттерны как здесь, так и на более низких по иерархии уровнях.
ASP.NET все еще предоставляет нам небольшую роскошь: мы можем использовать один экземпляр контейнера в рамках Application Context . Некоторые фреймворки не допускают даже это.

273 7.6. Построение P owerShell cmdlets
Некоторые фреймворки совсем не предоставляют швов, которые позволяют нам управлять жизненным циклом основных элементов фреймворка. Windows PowerShell является одним из таких фреймворков.
П римечание
Прочитайте этот раздел, даже если вы не интересуетесь PowerShell. Я выбрал его, главным образом, в качестве примера последнего испытания механизма внедрения зависимостей. Я также мог остановить свой выбор на Managed MMC SDK, но он в столь многих других моментах не приятен для использования, что я предпочел использовать в качестве примера PowerShell.
Важным элементов PowerShell является cmdlet
(предполагаю, что это слово произносится как commandlet, но я видел только, что он пишется как cm dlet). Вы можете считать cmdlet продвинутой утилитой командной строки. cmdlet
– это класс, унаследованный от
Cmdlet
, и он должен иметь конструктор по умолчанию. Как и для ASP.NET это требование эффективно исключает любое использование Constructor Injection. Решение проблемы тоже аналогично: мы перемещаем
Composition Root в конструктор каждого cmdlet
. Единственное отличие – отсутствует встроенный Application Context, поэтому мы должны прибегнуть к самому низшему универсальному деноминатору: статическому классу.
П римечание
Я предпочитаю code sm ell (гнилой код) любому использованию ключевого слова static
, но по сравнению с анти-паттернами code sm ell'ы указывают только на потенциальные недостатки проектирования. В некоторых особых случаях использование подобных идиом оправдано, и это именно тот случай.
Вас может заинтересовать то, как это все отличается от анти-паттерна Service Locator. Как и для ASP.NET главное отличие заключается не в структуре кода, а в использовании паттерна. Вместо того чтобы пытаться использовать статический Service Locator в качестве виртуального ключевого слова new
, для каждого cmdlet мы используем его только один раз. Чтобы защитить себя в дальнейшем от неправильного использования, мы можем сделать
Composer внутренним и использовать его только для преобразования типов из различных сборок, как это продемонстрировано на рисунке 7-17.

274
Рисунок 7-17: Когда нет выхода для статического контейнера, мы можем сделать его внутренним и переместить в корневую сборку приложения. Все методы
Resolve возвращают классы, которые определены в других сборках. Таким образом, как только контейнер преобразовал реализатор, ни один из классов преобразованной иерархии зависимостей не имеет доступа к статическому контейнеру, поскольку все они находятся за пределами центральной сборки приложения, а контейнер находится внутри.
Результатом преобразования диаграммы зависимостей является класс, определенный в другой сборке, и этот класс не имеет доступа к статическому контейнеру, поскольку он находится внутри центральной сборки приложения. Реализатору cmdlet необходимо использовать соответствующие DI-паттерны, как например, Constructor Injection, для того, чтобы применять любые зависимости, и мы эффективно защищаем себя от угроз Service
Locator'а.
Давайте рассмотрим пример, который иллюстрирует этот принцип.
Пример: построение cmdlet'ов управления корзиной
Этот пример возвращает нас к шаблонному коммерческому приложению. Как и большинство других коммерческих приложений, это приложение имеет функциональност ь корзины покупок. Среди пользователей общепринятой является такая ситуация: добавлять элементы в свою корзину покупок, но потом покидать сайт и никогда больше сюда не заходить.
Несмотря на то, что хранение в эти дни обходится дешево, заинтересованные стороны проекта попросили, чтобы вы предоставили им гибкую возможность очищать осиротелые корзины на основании различных критериев. Они хотят иметь возможность выбирать осиротелые корзины на основании того, когда они в последний раз обновлялись. Если корзина содержит значительное количество товаров, то ее не следует удалять (или, может быть, ей необходимо предоставить более длительный льготный период), но общее количество должно быть рассчитано согласно всем текущим бизнес-правилам.
Кажется, что API написания сценариев отлично подошло бы для этого, поскольку администратор смог бы определять и составлять расписание простых скриптов очистки.
PowerShell здесь наиболее подходит благодаря своей продвинутой функциональности фильтрации и транспортировки.

275
Вы можете реализовать желаемое API с помощью двух cmdlet
'ов: один – для того, чтобы извлечь все корзины, а другой – для удаления корзины данного пользователя. Следующий листинг является примером того, как это могло бы выглядеть в интерактивной сессии.
Листинг 7-14: Удаление корзин, хранящихся более одного месяца
1.
PS C:\> Get-Basket
2.
LastUpdated Owner Total
3.
----------- ----- -----
4.
19.03.2010 20:5... ploeh 89,4000 5.
22.01.2010 19:5... ndøh 199,0000 6.
21.03.2010 09:1... fnaah 171,7500 7.
PS C:\> $now = [System.DateTime]::Now
8.
PS C:\> $month = [System.TimeSpan]::FromDays(30)
9.
PS C:\> $old = $now - $month
10.
PS C:\> Get-Basket | ? { $_.LastUpdated -lt $old } |
11.
Remove-Basket
12.
PS C:\> Get-Basket
13.
LastUpdated Owner Total
14.
----------- ----- -----
15.
19.03.2010 20:5... ploeh 89,4000 16.
21.03.2010 09:1... fnaah 171,7500 17.
PS C:\>
18.
Строка 5: Старая корзина
Строка 7-9: Рассчитывает дату закрытия
Строка 10-11: Удаляет старые корзины
Перед тем как вы начнете удалять корзины, вам хотелось бы просмотреть текущие корзины системы. Вы можете использовать пользовательский cmdlet Get-Basket для того, чтобы перечислить все корзины. Обратите внимание на то, что каждая корзина имеет три свойства, которые сообщают вам о том,
1   ...   15   16   17   18   19   20   21   22   ...   43


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