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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница12 из 43
1   ...   8   9   10   11   12   13   14   15   ...   43
xUnit Test Patterns (Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code
(New York: Addison-Wesley, 2007), которая является источником всех этих имен паттернов.
Одна из первых вещей, которую нужно сделать в этом тесте, заключается в создании
«дублера для т ест ирования» (Test Double) для
TimeProvider
, который будет возвращать экземпляр ы
DateTime
, как они определены, вместо того чтобы основываться на часах системы. В этом тесте я использую динамический mock фреймворк, называемый Moq
(
ht tp://code.google.com/p/m oq/
), чтобы определить, что свойство
UtcNow вернет тот же
DateTime
, пока не указано иное. Когда все определено, эта заглушка (Stub) внедряется в
Ambient Context .
Первый вызов
GetExchangeRateFor должен вызвать внутренний
CachingCurrency
Currency
, потому что еще ничего не находится в кэше, а два следующие вызова должны вернуть кэшированное значение, потому что срок еще не истек, в соответствии с
заглушкой
TimeProvider
С парой кэшированных вызовов настала пора времени продвинуться вперед, вы меняете
заглушку
TimeProvider
, чтобы вернуть экземпляр
DateTime
, для которого как раз истек срок действия кэша, и снова вызываете метод
GetExchangeRateFor
, ожидая, что он вызовет внутренний
Currency во второй раз, потому что действительные записи кэша теперь уже истекли.
Поскольку вы ожидаете, что внутренний
Currency вызывается дважды, вы, наконец, убеждаетесь в этом, говоря внутреннему
Currency
Mock, что метод
GetExchangeRateFor должен быть вызван ровно два раза.
Одна из многих опасностей Ambient Context заключается в том, что как только он назначен, он остается одинаковым, пока не будут изменен снова, но в связи с его неявной природой, об этом можно легко забыть. В модульном тесте, например, поведение, определенное с помощью теста в листинге 4-15, остается одинаковым, если не будет явного сброса (что я делаю в Fixture Teardown). Это может привести к серьезным ошибкам (на этот раз в моем тестовом коде), потому что это распространится и загрязнит тесты, которые выполняются после этого теста.
Ambient Context выглядит обманчиво простым в реализации и использовании и может привести ко многим трудным для поиска ошибкам. Его можно использовать, но делайте это только тогда, когда не существует лучшей альтернативы. Это как хрен: отлично подходит для определенных вещей, но определенно не универсален.

158
Связанные паттерны
Ambient Context может быть использован для моделирования Cross-Cutting Concern, хотя и требует, чтобы у нас была подходящая Local Default.
Если окажется, что зависимость вообще не является Cross-Cutting Concern, вы должны изменить DI стратегию. Если у вас есть Local Default, вы можете переключиться на внедрение в свойство, а в противном случае вы должны использовать внедрение в конструктор.

159 4.5. Р езюме
Паттерны, представленные в этой главе, являются центральной частью DI. Вооружившись
Com position Root и соответствующим набором DI паттернов, вы можете реализовать основы DI. При применении DI есть много нюансов и мелких деталей, которые нужно выучить, но паттерны покрывают основную механику, которая отвечает на вопрос: как я
могу внедрит ь зависимости?
Эти паттерны не являются взаимозаменяемыми. В большинстве случаев вашим первым выбором должно быть внедрение в конструктор, но бывают ситуации, когда один из других паттернов является лучшей альтернативой. Рисунок 4-12 показывает процесс принятия решений, который может помочь вам принять решение о выборе нужного паттерна, но если есть сомнения, выберите внедрение в конструктор: вы никогда сильно не ошибетесь в этом выборе.
Рисунок 4-12: В большинстве случаев вы должны выбирать внедрение в конструктор, но бывают ситуации, когда один из других DI паттернов подходит лучше.
Первая вещь, которую нужно рассмотреть, заключается в понимании того, является ли зависимость тем, что вам нужно, или тем, что у вас уже есть, но вы хотите, чтобы она коммуницировала с другими сопредельными операциями. В большинстве случаев, вам, вероятно, нужна зависимость, но в сценариях надстройки вы можете захотеть передать надстройке текущий контекст. Каждый раз, когда зависимость может отличаться от операции к операции, внедрение в метод является хорошим кандидатом для реализации.
Когда зависимость представляет Cross-Cutting Concern, выбор подходящего паттерна зависит от направления коммуникации. Если вам нужно только что-нибудь записать

160
(например, время, которое заняла операция, или то, какие значения передаются), лучшей альтернативой является перехват (о котором я расскажу в главе 9). Он также хорошо работает, если ответ, который вам нужен от него, уже включен в определение интерфейса.
Кэширование является прекрасным примером этого использования перехвата.
Если вам необходимо запросить Cross-Cutting зависимость для ответа, который не включен в оригинальный интерфейс, вы можете использовать Ambient Cont ext , только если у вас есть надлежащая Local Default, которая позволяет оформить сам контекст разумным поведением по умолчанию, которое работает для всех клиентов без явной настройки.
Когда зависимость не представляет Cross-Cutting Concern, Local Default все еще является решающим фактором, поскольку она может сделать явное внедрение зависимости необязательным – используется реализация по умолчанию, если не указана переопределяющая реализация. Этот сценарий может быть эффективно использован при помощи внедрения в свойство.
В любых других случаях применяется паттерн внедрения в конструктор. Исходя из рисунка 4-12, кажется, что внедрение в конструктор является последним оплотом, который входит в игру только тогда, когда все остальное не дает результатов. Это верно лишь отчасти, поскольку в большинстве случаев специализиро ванные паттерны не применяются, и по умолчанию на поле остается внедрение в конструктор. Его легче понять и проще надежно реализовать, чем любой из других DI паттернов. Вы можете построить целые приложения только с одним внедрением в конструктор, но понимание других паттернов может помочь вам в разумном выборе нужного в некоторых случаях, когда он не подходит.
В этой главе содержится систематический каталог, который объясняет, как вы должны внедрять зависимости в ваши классы. В следующей главе DI рассматривается с противоположной стороны, и в ней я расскажу, как не нужно обращаться с DI.

161 5. DI анти-паттерны
В гастрономическом плане Дания была одной из развивающихся стран в 1970х: я был там, но я никогда не страдал, потому что не знал ничего лучшего. В основном использовались мясо и картофель, но постепенно приходили и зарубежные идеи. Я думаю, что одна из причин заключалась в том, что это было на заре эры массового туризма.
Датчане массово отправлялись на юг, в другие части Европы, и самые смелые пробовали местную еду. После возвращения домой более молодое поколение все больше готовило пасту, но ни один итальянец не распознал бы датскую версию соуса болоньезе.
Вот, что я думаю, произошло. Какому-то предприимчивому датскому туристу понравилась тальятелла алла болоньезе (tagliatelle alla bolognese) так сильно, что она решила попытаться приготовить ее, когда вернулась домой. (Я предполагаю, что это была женщина, потому что мужчины в то время много не готовили). Она сделала все, чтобы вспомнить, что входит в соус, но это было не так просто из-за долгой поездки на автобусе обратно в Данию.
Что касается ингредиентов, грудинка и красное вино были забыты, прежде чем она покинула Италию, мясной бульон и куриная печень были потеряны из памяти где-то в
Австрии или Швейцарии, а овощи один за другим выпадали из головы во время долгого пути через (Западную) Германию. Когда она пересекла датскую границу, все, что осталось от оригинального рецепта, – это нарезанный лук и мясной фарш, и их подают только с одним типом пасты, которая легко доступна в Дании в то время - спагетти.
Мы ели блюдо, которое получилось в результате, в течение многих лет и любили его. В какой-то момент в 1980-х годах, в рецепт были добавлены томатная паста и орегано, чтобы сделать его более похожим на исходный. Это был рецепт, который я использовал более чем десять лет, пока кто-то не подсказал мне, что для улучшения вкусовых качеств можно добавить еще морковь, сельдерей, куриную печень, красное вино и так далее.
Смысл истории заключается в том, что я думал, что делаю рагу алла болоньезе (ragù alla bolognese), тогда как в действительности я не был даже близок к этому. Мне никогда не приходило в голову ставить под сомнение подлинность рецепта, потому что я вырос с ним. Хотя подлинность не является конечным критерием, блюдо, приготовленное по подлинному рецепту, намного вкуснее, и я не собираюсь возвращаться к моему старому рецепту.
В предыдущей главе я кратко сравнил паттерны проектирования с рецептами. Паттерн обеспечивает общий язык, который мы можем использовать, чтобы сжато обсудить сложное понятие, и рагу алла болоньезе – это такое понятие, потому что мы можем обсудить, как оно согласуется с тальятелле или лазаньей. С другой стороны, когда понятие (или, скорее, реализация) искривлено, мы получаем анти-паттерн.
П римечание
Анти-паттерн представляет собой описание часто встречающихся решений проблемы, которые несомненно приводят к негативным последствиям (William J. Brown et al.,
AntiPatterns: Refactoring Software, Architectures, and Projects in Crisis (New York: W iley
Computer P ublishing), 1998, 7).

162
Анти-паттерны часто бывают вызваны незнанием (как с моим соусом болоньезе), и их нужно избегать, а знание этих общих ловушек может помочь вам избежать их. Они являются более или менее формализованным способом описания распространенных ошибок, которые люди совершают снова и снова, независимо друг от друга.
В этой главе я опишу некоторые общие анти-паттерны, связанные с DI. За время моей карьеры я видел, как все они используются в той или иной форме, и, каюсь, я сам применял более чем один из них. Во многих случаях они казались искренними попытками применить DI в приложении, но без полного понимания основ DI, реализация приносила больше вреда, чем пользы.
Изучение этих анти-паттернов должно дать вам представление о том, каких ловушек нужно избегать в ваших первых DI проектах. Ваши ошибки не будут выглядеть точно так же, как мои или примеры, представленные здесь, но эта глава покажет вам, где кроются опасные признаки.
Анти-паттерны могут быть исправлены путем рефакторинга кода к одному из DI паттернов, приведенных в главе 4. Сложность исправления в каждом отдельном случае зависит от деталей реализации, но для каждого анти-паттерна я предоставлю некоторые обобщенные рекомендации, как провести рефакторинг по направлению к паттерну.
С овет
В этой главе я не уделяю слишком много места рефакторингу от DI анти-паттерна к DI паттерну, потому что это не главная тема этой книги. Если вы заинтересованы в получении дополнительной информации о том, как вы можете переместить существующее приложение в направлении DI, есть целая книга, где обсуждается рефакторинг таких приложений: Working Effectively with Legacy Code (M ichael Feathers,
W orking Effectively with Legacy Code (New York: Prent ice Hall, 2004)). Хотя в ней не рассматривается исключительно DI, она охватывает многие из тех же понятий, что и я в данной книге.
Анти-паттерны, описанные в этой главе, приведены в таблице 5-1. Рисунок 5-1 иллюстрирует структуру главы.
Таблица 5-1: DI анти-паттерны
Анти-паттерн
Описание
Control Freak
Зависимости управляются напрямую, в отличие от инверсии
управления (Inversion of Control, IoC).
Bastard Injection
Foreign Default используются как значения по умолчанию для зависимостей.
Constrained
Construction
Предполагается, что конструкторы имеют особенную, индивидуа льную сигнатуру.
Service Locator
Неявный сервис может подавать зависимости потребителю, но нет гарантии, что он это сделает.

163
Рисунок 5-1: Структура этой главы принимает форму каталога анти-паттернов. Каждый анти-паттерн описан так, что вы можете прочитать о нем отдельно от других анти- паттернов.
Внимание
Эта глава отличается от других глав, потому что большая часть кода, который я покажу вам, дает примеры того, как не реализовывать DI. Не пытайтесь повторить это дома!
Так же, как внедрение в конструктор является наиболее важным DI паттерном, Cont rol
Freak является самым доминирующим анти-паттерном. Он всеми силами предотвращает вас от применения любого надлежащего и нужного внедрения зависимостей, так что вам придется сосредоточить свою энергию на этом анти-паттерне, прежде чем заняться другими. С другой стороны, наиболее опасным является Service Locator, потому что создается впечатление, что он на самом деле решает проблему.
Остальная часть этой главы описывает каждый анти-паттерн более подробно. Вы можете прочитать ее от начала до конца или только о том, в чем вы заинтересованы: каждый анти- паттерн занимает отдельный раздел. Т ем не менее, если вы решили прочитать только об одном анти-паттерне, вы должны сосредоточить свое внимание на Control Freak.
5.1. Control Freak
5.2. Bastard Injection
5.3. Constrained Construction
5.4. Service Locator
5.5. Резюме

164 5.1. Control Freak
Что является противоположнос тью инверсии управления? Первоначально термин
инверсия управления был придуман, чтобы определить противоположнос ть нормальному положению дел, но мы не можем на самом деле говорить об анти-паттерне «Business as
Usual». Вместо этого, после долгих размышлений, я назвал его Cont rol Freak, чтобы описать класс, который никак не желает терять контроль над своими зависимостями.
Это происходит каждый раз, когда мы создаем новый экземпляр типа с помощью ключевого слова new
. Когда мы делаем так, мы явно заявляем, что собираемся контролировать жизненный цикл экземпляра и что никто не получит шанс на перехват этого конкретного объекта.
С овет
Частота появления в коде ключевого слова new является очень грубым показателем того, насколько код сильно связан.
Анти-паттерн Cont rol Freak появляется всякий раз, когда мы получаем экземпляр зависимости, прямо или косвенно используя ключевое слово new в любом месте, кроме
Composition Root.
П римечание
Хотя ключевое слово new является запахом кода, когда дело доходит до меняющихся
зависимост ей, вам не нужно беспокоиться об использовании его для ст абильных
зависимост ей. В целом ключевое слово new не является "незаконными", но вы должны воздерживаться от его использования, чтобы получить экземпляр ы меняющихся зависимостей.
Наиболее вопиющим примером Cont rol Freak является то, когда мы не делаем никаких усилий, чтобы ввести абстракции в наш код. Вы видели несколько примеров в главе 2, когда Мэри реализовала свое коммерческое приложение (раздел 2.1.1). В таком подходе нет попытки ввести DI, но даже там, где разработчики слышали о DI и компоновке, анти- паттерн Cont rol Freak часто может быть найден в различных вариациях.
В следующих разделах я покажу вам некоторые примеры, которые напоминают код, что я видел в производственном использовании. В любом случае, у разработчиков были лучшие намерения программирования, используя интерфейсы (program m ing to interfaces), но они никогда не понимали движущие силы и мотивацию.
Пример: обновление зависимостей
Многие разработчики слышали о принципе программировании, используя интерфейсы, но не понимают глубокий смысл всего этого. В попытке сделать правильную вещь или следовать советам и рекомендациям, они пишут код, который не имеет особого смысла.
В главе 2, вы видели пример
ProductService
, который использует экземпляр абстрактного класса
ProductRepository
(листинг 2-6), чтобы получить список

165 рекомендуемых товаров. В качестве напоминания, вот соответствующий метод по своей природе: public IEnumerable
GetFeaturedProducts(IPrincipal user)
{ return from p in this.repository.GetFeaturedProducts() select p.ApplyDiscountFor(user);
}
По сравнению с листингом 2-6 я опустил ограждающее условие, но характерный момент заключается в том, что переменная repository представляет абстрактный класс. В главе 2 вы заметили, как поле repository может быть заполнено через внедрение в конструктор, но я видел и другие, более наивные попытки, например, вот такую.
Листинг 5-1: Обновление
ProductRepository
1.
private readonly ProductRepository repository;
2.
public ProductService()
3.
{
4.
string connectionString =
5.
ConfigurationManager.ConnectionStrings
6.
["CommerceObjectContext"].ConnectionString;
7.
this.repository = new SqlProductRepository(connectionString);
8.
}
Строки 7: Напрямую создается новый экземпляр
Поле repository объявлено как абстрактный класс
ProductRepository
, поэтому любой член в классе
ProductService
(например,
GetFeaturedProducts
) будут разрабатываться при помощи интерфейса. Хотя это звучит правильно, но это не принесет особой пользы, потому что во время выполнения тип всегда будет
SqlProductRepository
. Нет никакой возможности перехватить или изменить переменную repository
, пока вы не поменяете код и перекомпилируете его.
Вы не получите многого, определив переменную как интерфейс или абстрактный класс, если вы ее жестко закодировали на то, чтобы она всегда имела конкретный тип.
Единственное небольшое преимущество заключается в том, что конкретный тип определяется только в одном или нескольких местах в программном коде, поэтому замена одной конкретной реализации другой не потребует крупного рефакторинга. В этом примере вы должны только довести другую реализацию
ProductRepository в конструктор, в то время как остальная часть
ProductService будет работать без изменений.
Обновление зависимостей напрямую при помощи new является лишь одним примером анти-паттерна Cont rol Freak. Прежде чем я перейду к анализу и возможным путям исправления проблемы, созданной Control Freak, давайте посмотрим на несколько примеров, которые дадут вам более полное представление о контексте и общих неудачных попытках решения некоторых из полученных проблем.
В этом конкретном примере очевидно, что решение не является оптимальным.
Большинство разработчиков попытается усовершенствовать свой подход, как вы увидите в следующем примере.

166
Пример: фабрика
Наиболее распространенная (ошибочная) попытка исправить явные проблемы с обновлением зависимости при помощи new включает в себя какую-то фабрику. Есть несколько вариантов, где речь идет о фабриках, и я вкратце опишу каждый из них:

Конкретная фабрика (Concrete Factory)

Абстрактная фабрика (Abstract Factory)

Статическая фабрика (Static Factory)
Если сказать Мэри Роуэн (из главы 2), что она может иметь дело только с абстрактным классом
ProductRepository
, она введет
ProductRepositoryFactory
, которая будет создавать экземпляр ы, нужные для того, чтобы работа была сделана. Давайте послушаем, как она обсуждает такой подход со своим коллегой Йенсом: я думаю, что их обсуждение будет охватывать варианты фабрик, которые я перечислил:
1   ...   8   9   10   11   12   13   14   15   ...   43


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