Контейнер IoC, проверьте на ошибки во время компиляции - PullRequest
12 голосов
/ 27 марта 2012

У меня простой вопрос.

Допустим, у меня есть решение .Net, с различными проектами, такими как некоторые библиотеки классов (bll, dal и т. Д.) И основным проектом, который может быть веб-приложением или wpf-приложением, это не имеет значения.

Теперь предположим, что я хочу использовать контейнер IoC (например, Windsor, Ninject, Unity и т. Д.) Для разрешения таких вещей, как валидаторы, репозитории, реализации общего интерфейса и тому подобное.

Я собрал все это вместе. Компилируется и работает нормально. Затем, однажды, я добавляю новый сервис, и в своем коде я просто пытаюсь разрешить его через контейнер IoC. Дело в том, что я забыл зарегистрировать его в конфигурации IoC.

Все компилируется, приложение развертывается и запускается. Все работает нормально, за исключением случаев, когда код страницы запрашивает этот новый сервис для контейнера, а контейнер отвечает: «Эй, я ничего не знаю об этом сервисе».

Вы получите сообщение об ошибке и удобную страницу ошибок. Вы проверите ошибку, увидите проблему и исправите ее. Довольно стандартный.

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

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

Мысли

РЕДАКТИРОВАТЬ - После некоторых ответов и комментариев кажется, что модульные тесты действительно единственный способ достичь этой функции.

Что я хотел бы знать, так это то, что если бы модульные тесты были - по какой-либо причине - невозможны, и, следовательно, IoC не мог быть протестирован во время компиляции, это помешало бы вам использовать контейнер IoC и выбрать прямую инстанцию по вашему коду? Я имею в виду, считаете ли вы слишком небезопасным и рискованным использование IoC и позднего связывания, и видите, что его преимущества перевешиваются этим «недостатком»?

Ответы [ 3 ]

10 голосов
/ 28 марта 2012

Компилятор не может проверить работу всей вашей программы. Тот факт, что ваша программа компилируется, не означает, что она работает правильно (даже без использования IoC). Для этого вам понадобятся как автоматические тесты, так и ручное тестирование. Это не означает, что вы не должны пытаться позволить компилятору делать столько, сколько он может, но держаться подальше от IoC по этой причине плохо, поскольку IoC предназначен для обеспечения гибкости, тестирования и поддержки вашего приложения. Без IoC вы не сможете должным образом протестировать свой код, а без каких-либо автоматических тестов практически невозможно написать любое поддерживаемое программное обеспечение разумного размера.

Однако гибкость, которую обеспечивает IoC, означает, что зависимости, которые есть у определенного фрагмента кода, больше не могут быть проверены компилятором. Так что вам нужно сделать это по-другому.

Некоторые инфраструктуры DI позволяют вам проверить правильность контейнера. Например, Simple Injector содержит метод Verify(), который просто перебирает все регистрации и разрешает экземпляр для каждой регистрации. Вызывая этот метод (или используя аналогичный подход) во время запуска приложения, вы обнаружите во время тестирования (разработчика), что-то не так с конфигурацией DI, и это предотвратит запуск приложения. Вы могли бы даже сделать это в модульном тесте.

Важно, однако, что тестирование конфигурации DI не требует особого обслуживания. Если вам необходимо добавить модульный тест для каждого типа, который вы регистрируете, чтобы проверить контейнер, вы потерпите неудачу, просто потому, что пропущенная регистрация (и, следовательно, отсутствующий модульный тест) будет причиной сбоя в первую очередь.

Это дает вам «почти» поддержку во время компиляции. Тем не менее, вы должны помнить о дизайне вашего приложения и о том, как вы соединяете все вместе. Вот несколько советов:

  1. Держитесь подальше от неявного внедрения свойства, когда контейнеру разрешено пропускать внедрение свойства, если он не может найти зарегистрированную зависимость. Это запретит вашему приложению работать быстро и приведет к NullReferenceException s позже. Явное внедрение свойства, когда вы заставляете контейнер вводить свойство, это нормально, однако используйте внедрение конструктора всегда, когда это возможно.
  2. Зарегистрируйте все корневые объекты явно, если это возможно. Например, явно зарегистрируйте все экземпляры ASP.NET MVC Controller в контейнере. Таким образом, контейнер может проверять полный граф зависимостей, начиная с корневых объектов. Вы должны зарегистрировать все корневые объекты в автоматическом режиме, например, используя отражение, чтобы найти все корневые типы. Например, MVC3 Integration NuGet Package Simple Injector содержит метод расширения RegisterMvcControllers, который сделает это за вас. Интеграционные пакеты других контейнеров содержат аналогичные функции.
  3. Если регистрация корневых объектов невозможна или невозможна, протестируйте создание каждого корневого объекта вручную во время запуска. Например, в классах ASP.NET Web Form Page вы, вероятно, вызовете контейнер из их конструктора (поскольку классы Page, к сожалению, должны иметь конструктор по умолчанию). Ключевым моментом здесь снова является нахождение их всех с помощью отражения. Найдя все классы Page с помощью отражения и создав их экземпляры, вы узнаете (во время запуска приложения или во время тестирования), есть ли проблема с вашей конфигурацией DI или нет.
  4. Пусть все службы, которыми управляет ваш контейнер IoC, имеют один общедоступный конструктор. Несколько конструкторов приводят к неоднозначности и могут привести к непредсказуемым последствиям для вашего приложения. Наличие нескольких конструкторов является анти-шаблоном .
  5. Существуют сценарии, в которых некоторые зависимости еще не могут быть созданы во время запуска приложения. Чтобы гарантировать, что приложение может быть нормально запущено, а остальная часть конфигурации DI все еще может быть проверена, абстрагируйте эти зависимости за прокси или абстрактной фабрикой.
4 голосов
/ 28 марта 2012

Что я хотел бы знать, так это то, что если бы модульные тесты были - по любой причине - невозможны, и, следовательно, IoC не мог быть протестирован во время компиляции, это помешало бы вам использовать контейнер IoC и выбратьпрямое создание экземпляров всего кода?

Вы представляете ложный выбор здесь: либо используйте контейнер, либо «прямое создание экземпляров всего кода».Но вы все еще можете практиковать внедрение зависимостей без какого-либо контейнера.Вместо этого:

public void Main(string[] args)
{
    var container = new Container();
    // ... register various types here...

    // only Resolve call in entire application
    var program = container.Resolve<Program>(); 

    program.Run();
}

Вы просто делаете это:

public void Main(string[] args)
{
    var c = new C();
    var b = new B(c);
    var a = new A(b);
    var program = new Program(a);
    program.Run();
}

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

Еще два замечания:

  1. вы отметили свой вопрос dependency-injection, поэтому я предполагаю, что вы действительноиспользование внедрения зависимостей в отличие от шаблона локатора службы.Другими словами, я предполагаю, что вы не выставляете и не вызываете контейнер во всем своем коде, что не является необходимым и не рекомендуется .Мой ответ больше не работает, если вы делаете Service Locator.Пожалуйста, посчитай это ударом по Сервис-Локатору, а не моим ответом. Внедрение в конструктор - лучший выбор.

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

2 голосов
/ 28 марта 2012

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

...