Главная страница   /   1.3. Что внедрять, а что не внедрять (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

1.3. Что внедрять, а что не внедрять

Базы данных – это отличный пример типов BCL, которые являются неустойчивыми зависимостями: даже если LINQ to Entities – это технология, которая содержится в BCL, ее использование подразумевает реляционную базу данных.

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

Стандартная библиотека классов .NET (Base Class Library) состоит из множества сборок. Каждый раз при написании кода, который использует тип из сборки стандартной библиотеки классов, вы добавляете в ваш модуль зависимость. В предыдущем разделе я рассуждал на тему того, как важно слабое связывание, и насколько программирование на основании интерфейсов является основополагающим.

Означает ли это, что вы не можете ссылаться ни на одну из сборок стандартной библиотеки классов и использовать их типы напрямую в приложении? Что если вам захочется применить XmlWriter, который определен в сборке System.Xml?

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

Seams (Швы)

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

Пример "Hello DI", который я создавал в разделе "Hello DI", содержит Seam между классами Salutation и ConsoleMessageWriter, как это проиллюстрировано на рисунке 1-12. Класс Salutation не зависит напрямую от класса ConsoleMessageWriter; скорее, он использует интерфейс IMessageWriter для записи сообщений. Вы можете разобрать приложение на части в месте этого Seam и смонтировать приложение заново с другими составителями сообщений.

Рисунок 1-12: Пример "Hello DI" из раздела "Hello DI" содержит Seam между классами Salutation и ConsoleMessageWriter, потому что класс Salutation выполняет запись только при помощи абстракции интерфейса IMessageWriter.

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

Стабильные зависимости

Многие модули в стандартной библиотеке классов и за ее пределами не представляют никакой угрозы для степени модульности приложения. Они содержат функциональность, допускающую повторное использование, которую вы можете использовать для того, чтобы сделать ваш код более кратким.

Модули стандартной библиотеки классов всегда доступны в рамках вашего приложения, поскольку для его запуска необходим .NET Framework. Концепция параллельной разработки не применима к этим модулям, поскольку они уже существуют, и вы всегда можете повторно использовать стандартную библиотеку классов в других приложениях.

По умолчанию вы можете считать, что большинство (но не все) типов, определенных в BCL в качестве безопасных или стабильных зависимостей – я называю их стабильными, потому что они уже присутствуют там, склонны к обратной совместимости, а их вызов имеет детерминированные последствия.

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

  • Класс или модуль уже существует.
  • Вы считаете, что новые версии не будут содержать разрушительных изменений.
  • Рассматриваемые типы содержат детерминированные алгоритмы.
  • Вы никогда не думаете о том, что вам придется заменять класс или модуль на что-то другое.

Как ни странно, DI-контейнеры сами по себе будут проявлять себя как стабильные зависимости, потому что они удовлетворяют всем критериям. Когда вы решаете взять за основу вашего приложения определенный DI-контейнер, вы рискуете спотыкаться об этот выбор на протяжении всего жизненного цикла приложения; тем не менее, это еще одна причина того, почему вы должны ограничить использование контейнера в Composition Root.

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

Обычно зависимости можно считать стабильными путем исключения: они стабильны, если они не являются неустойчивыми.

Неустойчивые зависимости

Введение в приложение Seams является дополнительной работой, поэтому вам следует это делать только при необходимости. Может существовать более одной причины необходимости изоляции зависимости за пределами Seam, но они тесно связаны с преимуществами слабого связывания, которое обсуждалось в разделе "Преимущества DI".

Такие зависимости можно узнать по их склонности создавать препятствия для одного или большинства из этих преимуществ. Они не стабильны, потому что они не обеспечивают достаточное основание для приложений, и по этой причине я называю их неустойчивыми зависимостями. Зависимость следует считать неустойчивой, если соблюдается любой из следующих критериев:

  • Зависимость вводит требование, согласно которому необходимо установить и сконфигурировать исполняющую среду для приложения. Реляционная база данных является архитипическим примером: если мы не скроем реляционную базу данных за Seam, мы никогда не сможем заменить ее на какую-либо другую технологию. Это требование также усложняет процесс установки и запуска автоматизированных модульных тестов.

Базы данных – это отличный пример типов BCL, которые являются неустойчивыми зависимостями: даже если LINQ to Entities – это технология, которая содержится в BCL, ее использование подразумевает реляционную базу данных.

Другие необрабатываемые ресурсы такие, как очереди сообщений, веб-сервисы и даже файловые системы также попадают в эту категорию. Пожалуйста, отметьте, что это не сами конкретные .NET типы являются неустойчивыми, но скорее то, что они подразумевают об исполняющей среде.

Признаками этого вида зависимостей является отсутствие слабого связывания и расширяемости, а также невозможность тестирования.

  • Зависимость еще не существует, но все еще разрабатывается. Очевидным признаком таких зависимостей является неспособность выполнять параллельную разработку.
  • Зависимость не установлена на всех машинах организации, занимающейся разработкой. Это может быть аргументом в пользу дорогих сторонних библиотек или зависимостей, которые не могут устанавливаться на все операционные системы. Наиболее универсальный признак – невозможность тестирования.
  • Зависимость содержит недетерминированное поведение. Это чрезвычайно важно в модульных тестах, поскольку все тесты должны быть детерминированными. Типичными источниками недетерминированности являются случайные числа и алгоритмы, которые зависят от текущих даты и времени.

Заметьте, что такие универсальные источники недетерминированности, как System.Random, System.Security.Cryptography.RandomNumberGenerator или System.DateTime.Now определены в mscorlib, поэтому вам не избежать ссылки на сборку, в которой они определены. Тем не менее, вам следует относиться к ним, как к неустойчивым зависимостям, потому что они склонны к разрушению тестируемости.

Неустойчивые зависимости являются центральным вопросом механизма внедрения зависимостей. Мы вводим Seams в приложение скорее для неустойчивых зависимостей, нежели для стабильных. Кроме того, они обязывают нас компоновать их при помощи механизма DI.

Теперь, когда вы поняли разницу между стабильными и неустойчивыми зависимостями, вы можете приступить к рассмотрению горизонтов области применения механизма внедрения зависимостей. Слабое связывание – это доминирующий принцип проектирования, поэтому механизм DI (как инструмент его реализации) должен быть в вашем коде повсюду. Не существует четкой границы между предметом механизма внедрения зависимостей и проектированием добротного программного обеспечения, но для того чтобы определить контекст остальной части книги, я быстро опишу, что какие вопросы охватывает механизм DI.