Комментарии 13
Почему не использовать простой Koin? Это джавовская привычка всё усложнять или есть какой-то реальный профит от Dagger 2?
В) Был у Вас проект чисто на Java с легаси в 4 года, что лучше? Поддерживать предыдущий вреймворк для DI или внедрить новый (
С) Все зависит от задач. Иногда лучше вложить в «болерплейт» но иметь более гибкий функционал. Если это домашний проект который может поддерживать 1-2 программиста и все свеженькое и только на Котлине — пожалуйста. Если у вас команда 40+ разработчиков и вы пилите монорепу то стоит задуматься.
Все еще зависит от вашей зоны ответсвенности и как вы оцениваете долгосрочные (3+ года) перспективы приложения. Junior & Intermediate обычно пушат за фэнси фреймворки, Senior задумываются о целесообразности в Architecture старается сделать так, чтобы при любом условии шоколадная масса как можно дольше не растекалась.
Потом все эти абстракции про хорошую архитектуру меня не сильно интересуют. Меня интересует:
1. Скрость разработки. Как быстро я смогу сделать новую фичу.
2. Простота сопровождения. Насколько долго нужно курить мануал, чтобы что-то сделать новому человеку на проекте. И чтобы было меньше способов выстрелить себе в ногу.
3. Насколько просто писать тесты.
Потому, например, мы на Android перешли на Kotlin и его синтаксический сахар очень упрощает нам жизнь.
Нет ни слова про инстанциирование и RAII, есть масса примеров когда RAII имеет место быть в языках с управляемой памятью.
В общем случае нет ничего плохого, что один класс инстанциирует внутри другой. Когда вы выносите зависимый класс в конструктор, то всего лишь меняет отношении с композиции на агрегацию и если в первом случае время жизни объектов гарантированно совпадает, то во втором уже — зависит.
Ни слова не говорится о видимости объектов. Если класс A публичный, а B и С пакетные? Для чего мне надо выставлять еще один тип(класс B) в публичный интерфейс?
Абсолютно не говорится о том, когда надо применять DI, какие плюсы и минусы этого решения, с какой гранулярностью надо применять DI.
Потом из-за таких статей видишь вагоны типов в публичном контракте либы и вызовы в глубинных классах container.Resolve(someType).
Такое ощущение, что автор просто наращивает трафик к своему бложику по андроид девелопменту.
В общем случае нет ничего плохого, что один класс инстанциирует внутри другой.
Тут можно поспорить. Использование оператора new порождает жесткую связанность.
Жесткая связанность не имеет в себе ничего плохого, если мы говорим о моделях или об утилитарных классах, которые не обращаются к любому источнику данных (файлы, БД, веб-сервисы и т/д). Скажем, глупо передавать через конструктор StringBuilder.
В остальных случаях жесткая связанность — это катастрофа, которая превращает написание юнит/интеграционных тестов в пытку.
Предположим, у нас есть класс, который использует сервис, обращающийся к внешнему АПИ.
И если мы инстанцируем его внутри класса, то это всегда будет реальное АПИ и мы не можем его замокать без хаков.
Да, есть PowerMock, рефлексия и так далее, но это уже жесткие хаки, которых хотелось бы избежать.
Предположим, у нас есть класс, который использует сервис, обращающийся к внешнему АПИ.В этом случае конечно да, но даже в вашем случае, намного важнее иметь четко обозначенный контракт сервиса, нежели фактическое место его инстанциирования. Если такой контракт есть, то вытащить контруктор в контейнер проблем не составит. А ведь могут написать и так, что в интерфейсе будет куча методов, которые нужны только конкретному клиенту. Первое что приходит в голову:
И если мы инстанцируем его внутри класса, то это всегда будет реальное АПИ и мы не можем его замокать без хаков.
if (!service.isInitialized()){
service.init(/* some parameters или например this*/);
// А текущйи тип что-нить реализует типа IParameterSource
}
И вроде DI и все за интерфейсами с контейнером, а в результате сложно трассируемая дичь. Хотя тестами покрыть такую муть не проблема.Код вроде такого, заменить на использование контейнера сильно проще.
new Service(AppContext.param1(), AppContext.patam2())
Лаконичный простой и понятный код легче модифицировать, чем пытаться что-то реализовать на будущее.
Т.е. я к тому, что DI далеко не панацея, если пишут г-но делы, то они и с DI такого понаделают.
Не знаю, но термин "dependency injection" мне звучит как законная цель антивируса. :D
Третий способ: пусть кто-нибудь ещё обрабатывает зависимости вместо нас
…
Недостатки
Неа. Основной недостаток всех DI-фреймворков — неявное динамическое связывание в рантайме. Это затрудняет анализ и рефакторинг кода, ослабляет контроль над выполнением, и делает возможным компиляцию и запуск кода с невалидными зависимостями. Хотя есть фреймворки типа Dagger, которые делают связывание при компиляции.
Для простого DI вообще не нужно никакого фреймворка. Делаете класс-контекст ручками так, как вам это заблагорассудится, и определяете в нем все зависимости. Например так:
// лучше назвать его ApplicationContext
class OurThirdPartyGuy {
fun provideClassC(){
return ClassC() //just creating an instance of the object and return it.
}
// каждый метод должен возвращать уже полностью настроенный объект и не содержать параметров
fun provideClassB(){
return ClassB(provideClassC())
}
fun provideClassA(){
return ClassA(provideClassB())
}
}
Разница лишь в том, что в фреймворк создает класс контекста автоматически на основании расставленных аннотаций и правил. И фреймворк зачастую делает несколько больше, нежели просто DI — например, управление конфигурациями, управление жизненным циклом объектов (scopes), AOP и проксирование методов. Но при необходимости все это так же не составит труда сделать ручками.
Это затрудняет анализ и рефакторинг кода, ослабляет контроль над выполнением, и делает возможным компиляцию и запуск кода с невалидными зависимостями
По пунктам:
Анализ и рефакторинг кода — если понимать как работает фреймворк — то ни капельки не затрудняет. Даже наоборот.
Ослабляет контроль над выполнением — каким образом?
Компиляцию и запуск кода с невалидными зависимостями — для этого есть тесты. Напортачили с зависимостями — тесты упали. Если тестов нет — это обнаружится при стартапе приложения. Деплоить сразу в прод никто же не станет?)
Но при необходимости все это так же не составит труда сделать ручками.
Зачем? Просто зачем писать руками то, что написано давно за нас?
Анализ и рефакторинг кода — если понимать как работает фреймворк — то ни капельки не затрудняет.
Очень даже. Возьмите чужой код, и попробуйте сразу разобраться, как он работает. Для анализа так или иначе вам придется использовать функции поиска вашей IDE типа "Find Usages...". При использовании фреймворка, где объекты не создаются въявную в коде и не содержат статических ссылок, такой анализ станет более трудоемким.
Ослабляет контроль над выполнением — каким образом?
Последовательность инициализации бинов не определена (бывают случаи, где это важно, например инициализация сервисов при стартапе), еще хуже обстоят дела с финализацией — не определено когда стопаются объекты и в какой последовательности. Концепция "областей живучести" (scopes — не знаю как перевести) работает не всегда так, как ожидается, например если вы инъектируете прототайп-бин в синглтон, то он такаже становится синглтонам, что критично для не thread-safe объектов. Использование AOP позволяет динамически модифицировать поведение объекта, что иногда запутывает (некоторые умудряются засандалить в AOP бизнес-логику).
Компиляцию и запуск кода с невалидными зависимостями — для этого есть тесты.
Для юнит-тестов используется как правило специально заряженный тестовый контекст, урезанный и мокированный. Опробовать реальный контекст Вы сможете только, подняв полное приложение, в окружении, наиболее приближенном к продакшну. А это уже интеграционное тестирование, и цикл у него несоизмеримо бОльший.
Зачем? Просто зачем писать руками то, что написано давно за нас?
А что такого супер магического написано-то? Как я уже сказал, типичный DI-фреймворк автоматически, то есть неявно, создает контекст приложения на основании большого числа факторов: модулей, правил, файлов конфигурации, аннотаций объектов, сканирования classpath… Поэтому на этапе чтения кода очень трудно определить как именно будет построен контекст. И это есть явный антипаттерн. А если убрать автоконфигурацию, то от вашего фреймворка ничего и не останется.
P.S. я не против DI-фреймворков, сам давно использую Guice. Но меня жутко бесит, когда для связи трех бинов сразу тащат в проект какой-нибудь Spring.
Основы внедрения зависимостей