Как всегда, это зависит ™. Ответ зависит от проблемы, которую вы пытаетесь решить. В этом ответе я попытаюсь рассмотреть некоторые общие мотивирующие силы:
Порадуйте меньшие базы кода
Если у вас есть 4000 строк кода конфигурации Spring, я полагаю, что база кода имеет тысячи классов.
Это вряд ли проблема, которую вы можете решить после факта, но, как правило, я предпочитаю отдавать предпочтение небольшим приложениям с меньшими базами кода. Если вы находитесь в доменно-управляемом дизайне , вы можете, например, создать базу кода для ограниченного контекста.
Я основываю этот совет на своем ограниченном опыте, поскольку большую часть своей карьеры я писал сетевой бизнес-код. Я мог бы предположить, что если вы разрабатываете настольное приложение, или встроенную систему, или что-то другое, то все сложнее разобрать.
Хотя я и понимаю, что этот первый совет легко наименее практичен, я также считаю, что он самый важный, и поэтому я включаю его. Сложность кода изменяется нелинейно (возможно, экспоненциально) в зависимости от размера базы кода.
Favor Pure DI
Хотя я все еще понимаю, что этот вопрос представляет существующую ситуацию, я рекомендую Pure DI . Не используйте DI-контейнер, но если вы используете, по крайней мере, используйте его для реализации основанной на соглашениях композиции .
У меня нет никакого практического опыта работы со Spring, но я предполагаю, что под конфигурационным файлом подразумевается XML-файл.
Конфигурирование зависимостей с использованием XML является худшим из обоих миров. Во-первых, вы теряете безопасность типов во время компиляции, но ничего не получаете. Файл конфигурации XML может быть настолько же большим, как и код, который он пытается заменить.
По сравнению с проблемой, которую она решает, файлы конфигурации внедрения зависимостей занимают неправильное место на часах сложности конфигурации .
Случай для введения крупнозернистой зависимости
Я могу привести аргументы в пользу крупнозернистой инъекции зависимостей. Я также могу привести довод в пользу мелкозернистого внедрения зависимостей (см. Следующий раздел).
Если вы вводите только несколько «центральных» зависимостей, то большинство классов может выглядеть так:
public class Foo
{
private readonly Bar bar;
public Foo()
{
this.bar = new Bar();
}
// Members go here...
}
Это по-прежнему соответствует композиции объектов Design Patterns , а не наследованию классов , поскольку Foo
создает Bar
. С точки зрения ремонтопригодности это все еще можно считать ремонтопригодным, потому что, если вам нужно изменить состав, вы просто редактируете исходный код Foo
.
Это едва ли менее ремонтопригодно, чем внедрение зависимостей. Фактически, я бы сказал, что проще редактировать класс, который использует Bar
, вместо того, чтобы следовать косвенному указанию, присущему внедрению зависимости.
В первом издании моей книги о внедрении зависимостей я делаю различие между изменчивыми и стабильными зависимостями.
Изменчивые зависимости - это те зависимости, которые вы должны рассмотреть для введения. Они включают
- Зависимости, которые должны быть переконфигурированы после компиляции
- Зависимости, разработанные параллельно другой командой
- Зависимости с недетерминированным поведением или поведением с побочными эффектами
Стабильные зависимости, с другой стороны, являются зависимостями, которые ведут себя четко определенным образом. В некотором смысле, вы могли бы утверждать, что это различие дает основание для грубой инъекции зависимостей, хотя я должен признать, что не полностью осознавал это, когда писал книгу.
Однако с точки зрения тестирования это усложняет юнит-тестирование. Вы больше не можете проводить юнит-тесты Foo
независимо от Bar
. Как объясняет JB Rainsberger , интеграционные тесты страдают от комбинаторного взрыва сложности. Вам буквально придется написать десятки тысяч тестовых случаев, если вы хотите охватить все пути путем интеграции даже 4-5 классов.
Контр-аргумент в том, что часто ваша задача не программировать класс. Ваша задача - разработать систему, которая решает некоторые специфические проблемы. Это мотивация развития, ориентированного на поведение (BDD).
Другой взгляд на это представлен DHH, который утверждает, что TDD приводит к повреждению конструкции, вызванному испытаниями . Он также поддерживает грубое интеграционное тестирование.
Если вы возьмете эту точку зрения на разработку программного обеспечения, тогда имеет смысл вводить грубую зависимость.
Случай для введения мелкозернистой зависимости
Тонкодисперсная инъекция зависимостей, с другой стороны, может быть описана как внедрение всех вещей!
Мое основное беспокойство в отношении инъекций грубой зависимости - это критика, высказанная Дж. Б. Райнсбергером. Вы не можете покрыть все пути кода тестами интеграции, потому что вам нужно написать буквально тысячи или десятки тысяч тестовых случаев, чтобы охватить все пути кода.
Сторонники BDD будут противостоять аргументу, что вам не нужно покрывать все пути кода тестами. Вам нужно только покрыть те, которые производят ценность для бизнеса.
Однако, по моему опыту, все «экзотические» пути кода также будут выполняться при развертывании большого объема, и если их не тестировать, многие из них будут иметь дефекты и вызывать исключения во время выполнения (часто исключения с нулевой ссылкой).
Это побудило меня предпочесть детализированное внедрение зависимостей, потому что оно позволяет мне тестировать инварианты всех объектов в изоляции.
Фавор функциональное программирование
Хотя я склоняюсь к детализированному внедрению зависимостей, я сместил акцент на функциональное программирование, в том числе и по другим причинам, потому что оно по сути тестируемо .
Чем больше вы переходите к SOLID-коду, тем более функциональным он становится . Рано или поздно вы можете сделать решающий шаг. Функциональная архитектура - это архитектура портов и адаптеров , а внедрение зависимостей - это тоже попытка портов и адаптеров . Разница, однако, в том, что язык наподобие Haskell обеспечивает реализацию этой архитектуры через систему типов.
Фаворит статически типизированного функционального программирования
На этом этапе я по сути отказался от объектно-ориентированного программирования (ООП), хотя многие проблемы ООП неразрывно связаны с основными языками, такими как Java и C #, больше, чем сама концепция.
Проблема с основными языками ООП состоит в том, что почти невозможно избежать проблемы комбинаторного взрыва, которая, не проверенная, приводит к исключениям во время выполнения. С другой стороны, статически типизированные языки, такие как Haskell и F #, позволяют кодировать многие точки принятия решения в системе типов. Это означает, что вместо того, чтобы писать тысячи тестов, компилятор просто скажет вам, справились ли вы со всеми возможными путями кода (в некоторой степени; это не серебряная пуля).
Кроме того, внедрение зависимости не работает . Истинное функциональное программирование должно отвергать все понятие зависимостей . В результате получается более простой код.
Резюме
Если я вынужден работать с C #, я предпочитаю внедрение детализированных зависимостей, потому что это позволяет мне охватить всю кодовую базу управляемым количеством тестовых случаев.
В конце концов, моя мотивация - быстрая обратная связь. Тем не менее, модульное тестирование - не единственный способ получить обратную связь .