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

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


Скачать 5.66 Mb.
НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
АнкорВнедрение зависимостей в .net
Дата14.12.2019
Размер5.66 Mb.
Формат файлаpdf
Имя файлаВнедрение зависимостей в .NET.pdf
ТипРуководство
#100226
страница15 из 43
1   ...   11   12   13   14   15   16   17   18   ...   43
Кажется, мы не можем переделать наш выход из цикла, так как же мы можем разорвать его?
П рерывание цикла
Нам нужно найти отношение, где мы сможем прервать цикл и ввести внедрение в свойство. В данном случае это легко, потому что отношение между WPF
Window и
ViewModel уже использует внедрение в свойство. Это и будет место прерывания.
Самым простым решением будет подключить что-нибудь еще и установить свойство
DataContext для
MainWindow в последнюю очередь перед показом. Это возможно, но не особенно дружелюбно по отношению к DI контейнерам, потому что это потребовало бы от нас явно присвоить зависимость, после того как построение было выполнено.
В качестве альтернативы мы можем инкапсулировать отложенное присвоение в адаптер отложенной загрузки. Это позволяет соединить все должным образом с DI контейнером.
П римечание
В следующем примере показан этот же проект, который также описан в разделе 7.4.2. Вы можете увидеть весь код в примерах для книги.
Давайте посмотрим, как инкапсулироват ь создание реализации
IWindow
, которая правильно загружает
MainWindowViewModel и присваивает ее экземпляру W PF
MainWindow
. Чтобы помочь сделать это, вы вводите эту абстрактную фабрику: public interface IMainWindowViewModelFactory
{
MainWindowViewModel Create(IWindow window);
}
Класс
MainWindowViewModel имеет более чем одну зависимость, но все зависимости, кроме
IWindow
, могут быть удовлетворены сразу, так что вам не нужно передавать их в качестве параметра методу
Create
. Вместо этого вы можете внедрить их в конкретную реализацию
IMainWindowViewModelFactory
Вы используете
IMainWindowViewModelFactory как зависимость в реализации
IWindow
, унаследованной от
WindowAdapter
, что представлен в листинге 6-2. Это позволяет отложить инициализацию реализации
IWindow
, пока не будет вызван первый метод. Здесь вы видите, как переписывается метод
CreateChild из листинга 6-2: public override IWindow CreateChild(object viewModel)
{ this.EnsureInitialized(); return base.CreateChild(viewModel);
}
Перед выполнением реальной работы вы должны убедиться, что все зависимости полностью инициализированы. Когда они инициализированы, вы можете безопасно вызвать базовую реализацию.

214
Следующий листинг показывает, как реализуется метод
EnsureInitialized при помощи внедренной
IMainWindowViewModelFactory
Листинг 6-3: Отложенная инициализация зависимостей
1.
private void EnsureInitialized()
2.
{
3.
if (this.initialized)
4.
{
5.
return;
6.
}
7.
var vm = this.vmFactory.Create(this);
8.
this.WpfWindow.DataContext = vm;
9.
this.DeclareKeyBindings(vm);
10.
this.initialized = true;
11.
}
Строка 7: Создать
ViewModel
Строка 8: Внедрить
ViewModel в
Window
При инициализации
MainWindowAdapter вы в первый раз вызываете внедренную абстрактную фабрику для создания желаемой
ViewModel
. Это возможно на данный момент, потому что экземпляр
MainWindowAdapter уже создан, и поскольку он реализует
IWindow
, вы можете передать экземпляр методу
Create
Когда у вас есть
ViewModel
, вы можете присвоить ее
DataContext инкапсулированного
W PF
Window
. С небольшой дальнейшей настройкой
Window теперь полностью инициализирован и готов к использованию.
В Composition Root приложения вы можете подключить все это вот так:
IMainWindowViewModelFactory vmFactory = new MainWindowViewModelFactory(agent);
Window mainWindow = new MainWindow();
IWindow w = new MainWindowAdapter(mainWindow, vmFactory);
Переменная
MainWindow становится свойством
WpfWindow в листинге 6-3, а vmFactory соответствует полю с одноименным названием. При вызове методов
Show или
ShowDialog для результирующего
IWindow вызывает ся метод
EnsureInitialize и все зависимости удовлетворены.
Эта комбинация отложенной инициализации и помощи абстрактной фабрики может быть хорошим дополнительным штрихом, но на первом месте – это наличие внедрения в свойство, которое позволяет разорвать порочный круг. В данном случае вам «повезло», потому что WPF
Window уже использует внедрение в свойство через свое свойство
DataContext
Всегда имейте в виду, что лучший способ решить проблему цикла – это пересмотреть API, так чтобы цикл исчез. Тем не менее, в тех редких случаях, когда это невозможно или крайне нежелательно, мы должны разорвать цикл при помощи внедрения в свойство, по крайней мере, в одном месте. Это позволяет составить остальную часть графа объекта отдельно от зависимости, связанной со свойством. Когда остальная часть графа объекта полностью заполнена, то можно внедрить соответствующий экземпляр с помощью

215 свойства. В качестве дополнительного шага мы можем инкапсулироват ь эту логику назначения свойства в классе и использовать абстрактную фабрику, чтобы присвоить значение свойства в самый последний момент.
Внедрение в свойство сигнализирует о том, что зависимость не является обязательной, поэтому это изменение не должно нам даваться легко. Внедрение в конструктор намного предпочтительнее в большинстве случаев, но оно может быть сложным для некоторых людей. Давайте посмотрим, почему.

216 6.4. Обсуждение феномена Constructor
Over-injection
Если у вас нет специальных требований, внедрение в конструктор должно быть вашим предпочтительным паттерном внедрения. Т ем не менее, некоторым людям неудобно, если число зависимостей растет. Они не любят конструкторы со слишком большим числом параметров.
В этом разделе мы рассмотрим очевидную проблему растущего числа параметров конструктора и то, почему это хорошо, а не плохо. Как вы увидите, это не значит, что мы должны принять длинные списки параметров в конструкторах, поэтому мы также рассмотрим, что мы можем сделать со слишком большим числом аргументов конструктора. В данном разделе есть пример.
Распознание и решение проблемы Constructor Over-injection
Когда список параметров конструктора становится слишком большим, мы называем это явление Constructor Over-injection и считают его плохо пахнущим кодом. Этот плохо пахнущий код не появляется, но усугубляется в результате использования DI. Хотя нашей начальной реакцией может быть то, что нам не нравится внедрение в конструктор из-за
Constructor Over-injection, мы должны быть благодарны, что общая проблема проектирования открывается нам.
В этом разделе мы сначала найдем время, чтобы оценить, как Constructor Over-injection немного облегчает нашу жизнь, а затем рассмотрим соответствующие последствия.
C onstructor O ver-injecti on как сигнал
Хотя внедрение в конструктор легко реализовать и использовать, оно доставляет неудобство, когда конструкторы начинают выглядеть так: public MyClass(IUnitOfWorkFactory uowFactory,
CurrencyProvider currencyProvider,
IFooPolicy fooPolicy,
IBarService barService,
ICoffeeMaker coffeeMaker,
IKitchenSink kitchenSink)
Я не могу сказать, что я кого-то обвиняю, если он не любит такой конструктор, но я не виню внедрение в конструктор. Я могу согласиться, что конструктор с шестью параметрами сигнализирует о плохо пахнущем коде, но это свидетельст вует о нарушении
Принципа единственной обязанност и, а не о проблемах, связанных с DI.
С овет
Внедрение в конструктор позволяет легко определить нарушения принципа единственной обязанности.
Вместо того чтобы чувствовать неловкость из-за Constructor Over-injection, мы должны принять его как удачный побочный эффект внедрения в конструктор. Это сигнал, который

217 предупреждает нас всякий раз, когда класс берет на себя слишком большую ответственность.
Моим личным порогом являются четыре аргумента конструктора. Всякий раз, когда я добавляю третий аргумент, я начинаю рассматривать вопрос, могу ли я спроектировать вещи по-другому, но я могу жить с четырьмя аргументами для нескольких классов. Ваш лимит может быть другим, но когда вы его пересекаете, приходит время рефакторинга.
Т о, как мы проводим рефакторинг определенного класса, который слишком сильно вырос, зависит от конкретных обстоятельств: на месте ли объектная модель, домен, бизнес- логика и так далее. Разделение Божественного класса (God Class) на более мелкие, более сфокусированные классы в соответствии с известными паттернами проектирования – это всегда хороший ход.
Т ем не менее, бывают случаи, когда бизнес требования обязывают нас делать много разных вещей в одно и то же время. Это часто случается в пограничной области приложения. Подумайте о крупнозернистой операции веб сервиса, которая запускает много бизнес событий. Один из способов моделирования таких операций заключается в сокрытии множества зависимостей за Фасадным и сервисами (Facade Services).
Ре факторинг по направлению к Ф асадны м сервисам
Есть много способов, как мы можем разработать и осуществить необходимые операции, так чтобы они не нарушали принцип единственной обязанности. В главе 9 мы обсудим, как паттерн проектирования Декорат ор (Decorator) может помочь нам со стеком Cross-
Cutting Concerns, вместо внедрения их в потребляющие элементы в виде сервисов. Это может устранить лишнее число аргументов конструктора.
Т ем не менее, в некоторых случаях единая точка входа должна организовать много зависимостей. Одним из примеров является операция веб сервиса, которая запускает сложное взаимодействие различных сервисов. Точка входа пакета информации может столкнуться с той же проблемой.
На рисунке 6-11 показано, как мы можем провести рефакторинг ключевых отношений по направлению к фасадным сервисам.

218
Рисунок 6-11: На самой верхней диаграмме видно, что у потребителя есть пять зависимостей, которые являются сильным признаком того, что нарушен принцип единственной обязанности. Тем не менее, если роль потребителя заключается в том, чтобы организовать эти пять зависимостей, мы не можем какую-либо выбросить. Вместо этого мы можем ввести фасадные сервисы, которые организуют части отношений. В нижней части диаграммы у потребителя есть только две зависимости, а у фасадов две и три зависимости.
Рефакторинг к фасадным сервисам – это просто трюк, чтобы избавиться от слишком большого числа зависимостей. Ключом является определение естественных кластеров взаимодействия. На рисунке 6-11 показано, что зависимости
A

C
формируют естественный кластер взаимодейст вия, и также делают
D
и
Е
Благотворный сторонний эффект заключается в том, что открытие этих естественных кластеров раскрывает ранее неизвестные отношения и доменные понятия. В ходе этого процесса мы обращаем неявные понятия в явные понятия. Каждый фасад становится сервисом, который фиксирует это взаимодействие на более высоком уровне, и единственная обязанность потребителя заключается в том, чтобы организовать эти высокоуровневые сервисы.
Примечание
Фасадные сервисы являются абстрактными фасадами, как следует из названия.
Фасадные сервисы связаны с Param eter Objects, но вместо объединения и раскрытия компонентов, фасадный сервис раскрывает только инкапсулированное поведение, скрывая компоненты.

219
Очевидно, что мы можем повторить этот рефакторинг, если у нас есть такая сложное приложение, что у потребителя появляется слишком много зависимостей в фасадных сервисах. Создание фасадов для фасадных сервисов является вполне разумным шагом.
В пограничных сферах нашего приложения (например, пользовательский интерфейс или веб сервис) мы можем работать с набором крупнозернистых абстракций. Рассматривая реализации зависимостей, мы видим, что за крупнозернистыми сервисами стоят тонкозернистые (детальные) сервисы, которые являются комбинациями еще более детальных сервисов. Это позволяет нам быстро все рассмотреть на первичном уровне, обеспечивая при этом то, что каждая окончательная реализация придерживает ся принципа единственной обязанности.
Давайте рассмотрим пример.
Пример: рефакторинг приема заказов
Примеру коммерческого приложения, к которому мы обращаемся на время от времени, нужно иметь возможность получать заказы. Это часто лучше всего делать в отдельном приложении или подсистеме, потому что в этот момент меняется семантика транзакции.
Когда вы работаете с покупательской корзиной, вы можете динамически вычислить удельные цены, курсы валют, а также скидки, но когда клиент делает заказ, все эти значения должны быть получены и заморожены так, как они были предоставлены, когда покупатель одобрил заказ. В таблице 6-2 представлен процесс приема заказа.
Таблица 6-2: Когда подсистема заказов получает новый заказ, она должна выполнить различные операции
Действие
Т ребуемые зависимости
Сохранить заказ
OrderRepository
Отправить имейл о заказе покупателю
IMessageService
Сообщить системе учета сумму счета
IBillingSystem
Выбрать лучшие склады, чтобы подобрать и отправить заказ на основе товаров в заказе и близости к адресу доставки
ILocationService
,
IInventoryManagement
Запросить выбранные склады подобрать и отправить весь заказ или часть его
IInventoryManagement
Пять различных зависимостей требуется для того, чтобы просто получить заказ.
Представьте себе, сколько других зависимостей вам нужно будет обработать по другим операциям, связанным с заказами!
Давайте сначала рассмотрим, как это будет выглядеть, если потребляющий класс
OrderService непосредственно импортировал все эти зависимости, а в дальнейшем вы увидите, как можно провести рефакторинг функционала с помощью фасадных сервисов.
С лишком много де тальных зависимостей
Если вы позволите
OrderService непосредственно потреблять все пять зависимостей, структура будет такой, как показано на рисунке 6-12.

220
Рисунок 6-12: У
OrderService есть пять зависимостей, и это сигнализирует о том, что нарушен принцип единственной обязанности.
Если вы используете внедрение в конструктор для класса
OrderService
, у вас будет конструктор с пятью параметрами. Это слишком много, и указывает на то, что
OrderService имеет слишком много обязанностей. С другой стороны, все эти зависимости необходимы, поскольку класс
OrderService должен реализовать все необходимые функции, когда он получает новый заказ.
Вы можете решить эту проблему, переделав
OrderService
Рефакторинг по направлению к фасадным сервисам
Первое, что вам нужно сделать, это посмотреть на естественные кластеры взаимодейст вия с целью выявления потенциальных фасадных сервисов. Взаимодействие между
ILocationService и
IInventoryManagement должны немедленно привлечь ваше внимание, потому что вы используете их, чтобы найти ближайшие склады, которые могут выполнить заказ. Потенциально это может быть более сложный алгоритм, но после того как вы выбрали склады, вам нужно уведомить их о заказе.
Если вы подумаете об этом чуть больше, то оказывается, что
ILocationService является деталью реализации уведомления соответствующих складов о заказе. Все взаимодействие может быть скрыто за интерфейсом
IOrderFulfillment
, как показано на рисунке 6-13.
Интересно, что выполнение заказов звучит очень похоже на концепцию; есть вероятность, что вы только что обнаружили неявную доменную концепцию и сделали ее явной.
Рисунок 6-13: Взаимодейст вие между
IInventoryManagement и
ILocationService осуществлено в классе
LocationOrderFulfillment
, который реализует интерфейс
IOrderFulfillment
. Потребители интерфейса
IOrderFulfillment понятия не имеют, что у реализации есть две зависимости.

221
Реализация по умолчанию
IOrderFulfillment потребляет две исходные зависимости, поэтому имеет конструктор с двумя параметрами, а это хорошо. Как дополнительное преимущество, вы инкапсулировали алгоритм для нахождения лучших складов по данному заказу в многократно используемый компонент.
Этот рефакторинг объединяет две зависимости в одну, но оставляет вас с четырьмя зависимостями класса
OrderService
. Вам нужно искать другие возможности для объединения зависимостей в фасад.
Следующая вещь, которую вы могли заметить, заключается в том, что все требования включают уведомления других систем о заказе. Это означает, что вы можете определить общую абстракцию, которая моделирует уведомления, возможно, что-то вроде этого фрагмента кода: public interface INotificationService
{ void OrderAdded(Order order);
}
Совет
Паттерн проектирования Domain Event (доменное событие) может быть хорошей альтернативой для данного сценария.
Каждое уведомление внешней системы может быть реализовано с помощью этого интерфейса. Вы можете даже рассмотреть обертывание
OrderRepository в
INotificationService
, но вполне вероятно, что классу
OrderService будет необходим доступ к другим методам
OrderRepository для реализации других функций. Рисунок 6-14 показывает, как вы реализуете другие уведомления при помощи
INotificationService
Рисунок 6-14: Каждое уведомление внешней системы может быть спрятано за
INotificationService
: даже новый интерфейс
IOrderFulfillment
, который вы только ввели.
Вы можете удивиться, чем же это помогает, потому что вы обернули каждую зависимость в новый интерфейс. Количество зависимостей не уменьшилось, и это мне поможет?
Да. Поскольку все три уведомления реализуют один интерфейс, вы можете обернуть их в
Ком поновщ ик (Composite). Это еще одна реализация
INotificationService
, которая обрабатывает коллекцию экземпляров
INotificationService и вызывает метод
OrderAdded для них всех.

222
С концептуальной точки зрения это также имеет смысл, поскольку с высокоуровневого представления вы не заботитесь о деталях того, как
OrderService уведомляет другие системы. Т ем не менее, вас волнует, что он делает. Рисунок 6-15 показывает конечные зависимости
OrderService
Рисунок 6-15: Окончательный
OrderService с зависимостями после рефакторинга. Вы оставляете
OrderRepository как отдельную зависимость, потому что вам нужны его дополнительные методы для реализации других функций
OrderService
. Все другие уведомления скрыты за интерфейсом
INotificationService
. Во время выполнения вы используете
CompositeNotificationService
, который содержит оставшиеся три уведомления.
Это уменьшает
OrderService до двух зависимостей, что является намного более разумным числом. Функционально сть не изменилась, что делает это истинным рефакторингом. С другой стороны, изменился концептуальный уровень
OrderService
. В его обязанности сейчас входит: получить заказ, сохранить его и уведомить другие системы. Детали того, какие системы извещены и как это реализовано, были вытеснены на более детальный уровень.
Даже если вы везде последовательно использовали внедрение в конструктор, ни один конструктор класса не должен требовать более двух параметров
(
CompositeNotificationService принимает
IEnumerable
как один аргумент).
Constructor Over-injection – это не проблема, связанная с DI в целом или, в частности, с внедрением в конструктор. Скорее, это сигнал о том, что класс имеет слишком много обязанностей. В этом классе плохо пахнет код, а не внедрение в конструктор; и как всегда, мы должны внимательно отнестись к плохо пахнущему коду и улучшить его.
Есть много способов, которыми мы можем провести рефакторинг к паттернам, но одним из вариантов является введение фасадных сервисов, которые моделируют концепции на более высоком уровне абстракции. Это решает проблему нарушения принципа единственной обязанности и часто в процессе раскрывает ранее неизвестные доменные понятия.
Это один из многих способов, когда DI помогает нам лучше написать код. Поскольку слабая связанность является настолько ценной, мы хотим убедиться, что слабосвязанный код остается слабосвязанным. В следующем разделе рассматривается, как это сделать.

223 6.5. Мониторинг связывания
Слабая связанность является ценной, но это удивительно легко – ввести сильную (тесную) связанность. Все, что нужно, это начинающий разработчик и малейшая неосторожность, и будут введены жесткие ссылки. В Visual Studio можно легко добавить новые ссылки на существующий проект, но часто мы хотим того избежать. Необходимо соблюдать дисциплину, чтобы гарантировать, что каждый модуль фокусируется на своей зоне ответственности.
В этом разделе мы рассмотрим некоторые техники, которые могут быть полезны, когда мы хотим убедиться, что слабосвязанный код остается слабосвязанным. Возможно, мы хотим защитить код от своих ошибок, или, возможно, джуниор разработчикам в команде нужно немного помощи.
Ничто не сравнится с человеческим взаимодействием, когда речь заходит о передаче знаний. Парное программирование является идеальным, но это все же хорошая идея, просматривать то, что написано вручную, при помощи автоматизированных инструментов. В следующих разделах мы рассмотрим, насколько полезным может быть автоматизированное тестирование, а также изучим специализиро ванный инструмент, который называется NDepend.
Связывание при юнит тестировании
Если у нас есть набор юнит тестов, с которым мы регулярно работаем, мы можем быстро добавить несколько модульных тестов, посвященных изучению зависимости, и они провалятся, если будет обнаружена необоснованная зависимость. Используя систему типов .NET , мы легко можем написать модульный тест, который пройдет циклом по всем ссылкам сборки и провалится, если найдет что-то там, чего там быть не должно.
В примере коммерческого приложения уже есть юнит тесты, так что вы легко можете добавить еще один. Следующий листинг показывает модульный тест, который защищает модуль логики представления (Presentation Logic m odule) от прямого обращения к модулю доступа к данным на основе SQL Server (SQL Server–based Data Access module).
Листинг 6-4: Обеспечение слабой связанности при помощи юнит тестирования
[Fact] public void SutShouldNotReferenceSqlDataAccess()
{
// Fixture setup
Type sutRepresentative = typeof(HomeController); var unwanted = "Ploeh.Samples.Commerce.Data.Sql";
// Exercise system var references = sutRepresentative.Assembly
.GetReferencedAssemblies();
// Verify outcome
Assert.False( references.Any(a => a.Name == unwanted), string.Format(
"{0} should not be referenced by SUT", unwanted));
// Teardown
}

224
Этот тест ищет зависимости модуля презентационной логики. Чтобы получить список ссылок, вам нужно запросить сборку. Вы можете получить сборку из любого типа, содержащегося в этой сборке, так что вы можете выбрать один. Часто лучше всего выбирать тип, который останется в сборке на долгое время, потому что иначе вам придется переписывать тест, если выбранный тип будет удален. В данном тесте выбран
HomeController
, потому что у сайта всегда будет главная страница.
Кроме того, необходимо определить сборку, на которую не должны ссылаться. Вы можете использовать ту же технику и выбрать репрезентативный тип из этой сборки, но это будет означать, что вы должны сослаться на эту сборку из модульного теста. Это не так плохо, как ссылки на нежелательную сборку из рабочего кода, но это все равно создаст искусственную связь между этими двумя библиотеками – можно сказать, они становятся
«уличенными в связи». Хотя безопасность типа желательна, слабая связанность имеет козыри в данном случае, поэтому вы определяете нежелательную сборку при помощи строки (но см. следующие обсуждения относительно других возможностей).
Получить сборку, на которую ссылаются, от репрезентативного типа так же просто, как один раз вызвать метод. Т еперь вы можете использовать простой LINQ запрос, чтобы убедиться, что ни одна из этих сборок, на которые ссылаются, не имеет нежелательного имени. В утверждении (прим. переводчика: в принципе ААА при юнит тестировании последнее А – это
Assert
, в данном случае «утверждение») вы также выводите сообщение, которое отображается, если утверждение не выполняется.
С овет
Это утверждение использует простой LINQ запрос, но вы можете заменить его циклом foreach
, если вы разрабатываете на .NET 3.0 или более ранних версиях.
С овет
Вы также можете изменить логику на обратную и написать тест, так чтобы только конкретные ссылки из заранее подготовленного списка были разрешены, а все другие ссылки считались незаконными.
Тестирование связанности при помощи Red/Green/Refactor
Если вы используете T DD для реализации вашего кода, вы привыкли к так называемому циклу разработки Red/Green/Refactor, где вы сначала пишете неудачный тест, затем он проходит успешно, и, наконец, вы меняете код, чтобы сделать его более легким в поддержке.
Оказывается, сделать так, чтобы тест, предотвращающий тесную связанность, не сработал, сложнее, чем вы думаете. Даже если целевой проект Visual Studio имеет ссылку на нежелательну ю зависимость, компилятор включит только ссылку, если она используется.
Т аким образом, чтобы сделать тест, который не сработает, мы должны сначала добавить нежелательну ю ссылку, а затем написать фиктивную строку кода (dumm y code), которая использует тип из нежелательной зависимости. Как только мы увидели, что тест не прошел, мы можем затем изменить процесс на противоположный, чтобы тест прошел.
Очевидно, что это не проблема, если тестируемая библиотека уже нарушает ограничение связанности.

225
В предыдущем примере юнит тест добавлен к существующему набору юнит тестов, предназначенному для модуля логики представления. Рисунок 6-16 иллюстрирует ссылки в действии.
Рисунок 6-16: Библиотека
PresentationLogicUnitTest
– это набор тестов, нацеленный на библиотеку
PresentationLogic
. Чтобы сделать это, данная библиотека должна содержать ссылку к своей цели, а также общие абстракции, которые определены в доменной модели. Поскольку
PresentationLogicUnitTest не нацелен на доменную модель, модуль
DomainModel показан серым цветом.
Листинг 6-4 определяет нежелательну ю сборку при помощи простой строки, но было бы более безопасно для типа определить его при помощи репрезентативного типа. Однако вам может потребоваться добавить в юнит тест ссылку к модулю доступа к данным на основе SQL Server (SQL Server–based Data Access m odule), как показано на рисунке 6-17.
Рисунок 6-17: Если мы хотим безопасности типов, добавив репрезентативный тип из библиотеки
SqlDataAccess в
PresentationLogicUnitTest
, мы вводим новую зависимость в набор юнит тестов по той единственной причине, что мы хотим убедиться, что это никогда не будет случайно добавлено в библиотеку
PresentationLogic

1   ...   11   12   13   14   15   16   17   18   ...   43


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