LINUX.ORG.RU

Используете Dependency Injection? Тогда у вас проблемы.

 ,


3

3

Далее будет описание ключевых проблем возникающих от использования одной из техник Inversion of Control (IoC): Dependency Injection (DI). Проблемы как проистекают как из самой идеи DI, так и из применения оной на языке java.

Первым делом небольшое соглашение: в данном случае полагается, что в DI имеет смысл помещать исключительно объекты подходящие под определение «сервиса». Для сервиса характерно:

  • Отсутствие или сокрытие состояния, напр. сервис выполняющий сериализацию в json, сервис выполняющий шифрование и сервис доступа к бд, кеш.
  • Разделение API: публичное (собственно API сервиса, как правило за него отвечает отдельный интерфейс) и управления (сюда входит и управление жизненным циклом сервиса и конфигурация)
  • Наличие характерных стадий жизненного цикла: создание реализации -> конфигурация -> работа (или обработка запросов) -> завершение

С учетом того что API управления должно быть скрытым от пользователя сервиса, то наиболее краткий вариант реализации сервиса будет в виде: http://ic.pics.livejournal.com/wayerr/33550674/1829/1829_900.png Допустим нашему сервису требуется доступ к парочке иных сервисов: http://ic.pics.livejournal.com/wayerr/33550674/1590/1590_900.png

Эта очевидная и удобная схема логично проистекает из самого языка программирования и правил хорошего кода (не всех). Однако в реальности разработчик столкнется с ситуацией когда у сервиса появится дополнительная зависимость, если это в рамках одного проект - то нет никаких проблем, в конструктор легко добавить параметр. Если же дело происходит в библиотеке которую используют другие проекты, то конструктор менять нельзя - это сломает API. Можно сделать второй конструктор, но множество DI фреймворков не поддерживают это (хуже того их поведение может быть не определено в этом случае).

Отлично, раз нам нужна расширяемость то используем конфигурацию сервиса через сеттеры: http://ic.pics.livejournal.com/wayerr/33550674/2102/2102_900.png На этом большинство разработчиков удовлетворяется. Но нет, проблемы подкрались незаметно:

  • Сервис не имеет стадии инициализации - т.е. у нашего сервиса нет той точки в коде, после которой можно с уверенностью заявить, что он корректно сконфигурирован. При этом конструктор объекта уже не выполняет своей роли.
  • API конфигурации публичен, а значит возможно изменение конфигурации в процессе работы. Особенно много радости от отладки эта возможность подарит в многопоточном окружении.
  • Так как очень часто возникает необходимость в выполнении некоторых действий после инициализации то появляются затейливейшие костыли вроде методов аннотированных @PostConstruct.

Чтобы избавится от этих проблем надо четко выделить стадию инициализации, нетрудно догадаться что для инициализации служит конструктор, значит вернемся к инициализации через конструктор, но немного другим образом: http://ic.pics.livejournal.com/wayerr/33550674/2359/2359_900.png

На диаграмме видно новый объект: конфигурация сервиса, он содержит только ссылки на объекты необходимые для инициализации сервиса. При таком подходе мы сохраняем расширяемость (в конфигурацию всегда можно добавть еще свойство). Вновь выполняет свои задачи конструктор сервиса, в нем теперь можно валидировать конфигурацию. API конфигурации скрыто от пользователя и сервис уже нельзя переконфигурировать во время работы.

Напоследок стоит отметить две вещи:

  • Большинство DI контейнеров, не поддерживают такой подход без дополнительной доработки.
  • Получившийся дизайн сервиса можно упростить если использовать Service Locator который передавать в конструктор, при этом мы получим все теже достоинства но без написания лишнего кода. Да, это уже не будет IoC, зато сохраняются все его преимущества и теряются недостатки.

зы. Да это опять я, и по традиции с картинками тут.

ps2. http://thecodelesscode.com/case/104?lang=ru&trans=smal - для тех кто скажет про документацию к сервису

Deleted

Получившийся дизайн сервиса можно упростить если использовать Service Locator который передавать в конструктор, при этом мы получим все теже достоинства но без написания лишнего кода. Да, это уже не будет IoC, зато сохраняются все его преимущества и теряются недостатки.

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

anonymous ()
Ответ на: комментарий от anonymous

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

Deleted ()
Ответ на: комментарий от Deleted

«Прозрачные» зависимости от этого не спасают

Ну от кривых рук ничего не спасает, да :)

в томже спринге легко можно сделать циклическую зависимость и огрести совершенно невменяемую ошибку при запуске

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

в случае SL ошибка будет хотябы точно указывать на проблемное место

Каким образом?

anonymous ()

API конфигурации скрыто от пользователя и сервис уже нельзя переконфигурировать во время работы.

Так пользователям надо давать доступ только к интерфейсу, тогда он не сможет изменить конфигурацию.

По-моему, проблема сильно преувеличена. 99% проектов с DI что я видел, конфигурят все бины либо через XML, либо аннотациями, и никаких изменений в рантайме уже нет. Тот 1% что хотят изменения в рантайме импользовали OSGi.

roy ★★★★★ ()

/development еще торт с такими постами.

Но нет ли в твоей стене текста посыла на Фаулера?

d_Artagnan ★★ ()
Ответ на: комментарий от d_Artagnan

Ну фаулер писал об этом, да только про конфигурацию у него не было, ибо он просто упомянул SL и что его «недооценивают» http://martinfowler.com/articles/injection.html

A common reason people give for preferring dependency injection is that it makes testing easier. The point here is that to do testing, you need to easily replace real service implementations with stubs or mocks. However there is really no difference here between dependency injection and service locator: both are very amenable to stubbing. I suspect this observation comes from projects where people don't make the effort to ensure that their service locator can be easily substituted. This is where continual testing helps, if you can't easily stub services for testing, then this implies a serious problem with your design.

Это один из примеров. Да стоит учесть что SL в библиотеке «не очень» ибо зависимость, однако в DI у нас также зависимость от анноатций и т.п.

Deleted ()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от roy

Так пользователям надо давать доступ только к интерфейсу, тогда он не сможет изменить конфигурацию.

Дизайн сервиса подразумевает что классы публичны, а значит сможет без рефлекшена.

99% проектов с DI что я видел, конфигурят все бины либо через XML, либо аннотациями, и никаких изменений в рантайме уже нет

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

Deleted ()
Ответ на: комментарий от Deleted

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

anonymous ()

Я правильно понял, основная проблема DI через конструктор это ломание обратной совместимости?

f1xmAn ★★★★★ ()

тут важна сама концепция, а не ограниченные реализации

как правило приложение изобилует временными решениями зачастую дикими от игнорирования возможностей инъекций зависимостей

сама возможность достойной альтернативы наследованию значительно упрощает задачу - вместо зоопарка классов можно обойтись сущностью поддерживающей нужный интерфейс

начать нужно инъекции с простейших вещей, но играющих немаловажную роль и посмотреть в сторону рефактринга синглтонов и статики

не нужно ничего делать про запас и без явной необходимости

проблема di контейнеров в том, что они не всегда реализуют ленивую загрузку и не всегда кэшируют результат в сервисе, а также недопонимание что должно быть фабрикой и почему там надо делать не getEntity а makeEntity

anonymous ()

используем конфигурацию сервиса через сеттеры
На этом большинство разработчиков удовлетворяется

За такое надо после работы п, ну, вы поняли. SOLID намекает и намекает правильно, что так делать нельзя.

bytecode ★★ ()
Ответ на: комментарий от f1xmAn

Да, кроме того у некоторых ломается хорошее настроение при виде метода у которого более 5 аргументов.

Deleted ()
Ответ на: комментарий от bytecode

За такое надо после работы п, ну, вы поняли. SOLID намекает и намекает правильно, что так делать нельзя.

What?

maloi ★★★★★ ()
Ответ на: комментарий от anonymous

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

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

да, если разрабатывается write-only приложение которое после релиза закапывается, а разрабы разбегаются - то тут можно писать как угодно

Deleted ()

Так как очень часто возникает необходимость в выполнении некоторых действий после инициализации то появляются затейливейшие костыли вроде методов аннотированных @PostConstruct.

я правильно понимаю, что ты предлагаешь все что запихивают в @PostConstruct выполнять в конструкторе?

maloi ★★★★★ ()
Ответ на: комментарий от Deleted

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

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

anonymous ()
Ответ на: комментарий от Deleted

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

А ты начни опять делать DI через сеттеры и твои волосы станут мягкими и шелковистыми - они как раз и нужны для того чтобы циклические зависимости можно было разрулить. С конструкторами и SL такое в принципе невозможно.

ЗЫ можно - не значит «всегда найдется решение», а значит что в отличие от SL и внедрения через конструкторы решение хотя бы иногда существует.

maloi ★★★★★ ()
Ответ на: комментарий от maloi

я предлагаю использовать конструкции языка по назначению, из этого вытекает и использование конструктора.

Deleted ()
Ответ на: комментарий от maloi

А ты начни опять делать DI через сеттеры и твои волосы станут мягкими и шелковистыми - они как раз и нужны для того чтобы циклические зависимости можно было разрулить. С конструкторами и SL такое в принципе невозможно.

Между всеми тремя подходами в случае циклических зависимостей разницы нет- если не использовать изкоробочную «ленивость» SL.

Deleted ()
Ответ на: комментарий от Deleted

я предлагаю использовать конструкции языка по назначению, из этого вытекает и использование конструктора.

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

maloi ★★★★★ ()
Ответ на: комментарий от Deleted

Между всеми тремя подходами в случае циклических зависимостей разницы нет

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

если не использовать изкоробочную «ленивость» SL.

она ничего не дает, ровно 0 преимуществ в случае циклических зависимостей.

maloi ★★★★★ ()
Ответ на: комментарий от maloi

аналогично SL не может вернуть не инициализированный объект

maloi ★★★★★ ()
Ответ на: комментарий от maloi

Конструкторы очень сильно ограничены по тому что в них можно а что нельзя делать.

При такой логике у тебя и final поля придуманы злодеями для пыток 8)

Deleted ()
Ответ на: комментарий от maloi

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

И в сеттере с ним что-то сделают, привет медовый месяц с отладчиком. И да, кажется я видел твой код 8).

она ничего не дает, ровно 0 преимуществ

Аргументированно, да.

Deleted ()

На мой скормный взгляд - подмена понятий. С каких это пор у вас конструктор это API в контекстe DI ? Мы же отталкиваемся от интерфейсов. Конструкторы конфигурятся в конфиге DI фрэймворка и меняются в любой момент, назаметно для клиентов.

В чём проблема то?

ii8_ ★★★★ ()
Ответ на: комментарий от ii8_

API: публичное (собственно API сервиса, как правило за него отвечает отдельный интерфейс) и управления (сюда входит и управление жизненным циклом сервиса и конфигурация)

С каких это пор у вас конструктор это API в контекстe DI ?
В чём проблема то?

В том что для среднестатистического программиста тут много букв и он в концу забыл что было в начале.

Deleted ()
Ответ на: комментарий от Deleted

Перед тем как обзываться, объяснил бы по рабоче-крестьянски что ты понимаешь под API публично, управления и т.д.

ii8_ ★★★★ ()
Ответ на: комментарий от Deleted

Ну дык этож кривая архитектура. Вот давай реальный пример когда так делать нужно? По прежнему не вижу проблемы. Есть веоятность что некоторые под DI понимают что-то своё, что-то странное. Перечитай книгу Spring in Action плиз.

ii8_ ★★★★ ()
Ответ на: комментарий от Deleted

При такой логике у тебя и final поля придуманы злодеями для пыток 8)

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

maloi ★★★★★ ()
Ответ на: комментарий от Deleted

И в сеттере с ним что-то сделают, привет медовый месяц с отладчиком. И да, кажется я видел твой код 8).

а я видел твой код, в котором в сеттеры пихают логику, да, мы с тобой квиты.

Аргументированно, да.

Ровно столько аргументов, сколько нужно.

maloi ★★★★★ ()
Ответ на: комментарий от maloi

конструкторы в джаве не очень подходят для сложного кода

Сложным кодом ты называешь что?

Deleted ()
Ответ на: комментарий от maloi

а я видел твой код, в котором в сеттеры пихают логику, да, мы с тобой квиты.

Что язык запрещает в сеттер запихнуть, например валидацию аргумента? Нет. А в это время туда некий «малой» передаст недоинициализированный объект. Все.

Deleted ()
Ответ на: комментарий от ii8_

Ну дык этож кривая архитектура.

Ты только просил разжевать на пальцах что такое API а теперь судишь о кривизне архитектуры.

Перечитай книгу Spring in Action плиз.

Ты на картинке видел типичный пример «хорошего спринг-бина» он у тебя вызвал мурашки, но ты все еще веришь в эту книгу.

Deleted ()
Ответ на: комментарий от Deleted

1. Умения ражёвывать «на пальца» - признак мастерства рассказчика. Сложною речь и аббревиатуры можно понимать по разному. 2. Это не простой бин. Так бины не пользуют. В доказательство этому ты даже не можешь привести пример из реального проекта. Конечно такое использование DI - кривая архитектура. 3. Книга норм.

ii8_ ★★★★ ()
Ответ на: комментарий от Deleted

Что язык запрещает в сеттер запихнуть, например валидацию аргумента? Нет.

язык не запрещает, здравый смысл запрещает. Если это конечно не банальная проверка на null (которая в нормальном коде заменяется @Required и дает в итоге больше)

maloi ★★★★★ ()

У жаба-индуса пердак заболел. Как это мило.

anonymous ()
Ответ на: комментарий от Deleted

Да

Ну значит такие обновления в либу нужно оставлять для мажорных релизов, чтобы клиенты либы были к этому готовы. Вариант с сеттерами, честно-говоря, говно. Вот правда. Есть очень хорошее правило хорошего кода — обязательные зависимости инжектить через конструктор. В таком случае, обновив либу, ты не получишь NPE в самый неудачный момент просто из-за того, что поленился внимательно прочесть release notes. Ну а про многопоточность и возможность переконфигурации сервиса ты уже писал. Вариант с передачей Configuration в конструктор мне не слишком нравится потому что если появится новая зависимость, а ты её не засетаешь, то компилятор об этом нифига не скажет, разве что в сам Configuration инжектить зависимости через конструктор. Имхо, лучшим вариантом из перечисленных является является инжектирование через конструктор без всяких Configuration классов, потому что:
1. О добавлении новой обязательной зависимости тебе скажет компилятор
2. Если зависимостей становится слишком много, это сразу становится видно, а значит нормальный разработчик вспомнит про SRP и сядет за рефакторинг
3. Нет необходимости в валидации зависимостей в конструкторе (принимая Configuration таки лучше проверить, что там есть все необходимое)

кроме того у некоторых ломается хорошее настроение при виде метода у которого более 5 аргументов.

Так не надо такое писать ;)

f1xmAn ★★★★★ ()
Ответ на: комментарий от ii8_

Умения ражёвывать «на пальца»

Бгг это лишь наличие свободного времени на тех кто подобен желудю.

В доказательство этому ты даже не можешь привести пример из реального проекта.

https://github.com/spring-projects/spring-security/blob/db531d9100a0afa1cac47...

3. Книга норм.

Заметно по исходникам спринга.

Deleted ()
Ответ на: комментарий от maloi

здравый смысл запрещает

Здравый смысл запрещает использовать мутабельные объекты в многопоточном окружении 8)

Deleted ()
Ответ на: комментарий от f1xmAn

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

Клиенты тебя проклянут.

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

И не должен говорить, иначе опять таки ломается обратная совместимость.

Если зависимостей становится слишком много, это сразу становится видно

Ты забываешь что там не только зависимости но и «настройки» а значит их может быть очень много изначально. Например как в апачевском http client.

Deleted ()
Ответ на: комментарий от Deleted

ты показал тупо сеттер. Это нормальная второй метод инжекта зависемостей в бин. Этот сеттер используется в xml конфиге. Это вот это ты назваешь паблик АПИ? Мало что недоучен так ещё и грубый))

ii8_ ★★★★ ()
Ответ на: комментарий от ii8_

Это вот это ты назваешь паблик АПИ?

Черт, я нашел желудь, он не только пишет на лоре, но еще и программы. Это api конфигурации.

Это нормальная второй метод инжекта зависемостей в бин.

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

Deleted ()
Ответ на: комментарий от Deleted

Лана, уговорил. Бог с тобой золатая рыбка. Удачи в сдаче школьных экзаменов! И да, уделяй больше гуманитарным наукам, - это твоё; api, pipi, sipi - выучит и добьёшься успеха.

ii8_ ★★★★ ()
Ответ на: комментарий от Deleted

Здравый смысл запрещает использовать мутабельные объекты в многопоточном окружении 8)

мутабельность и многопоточность тут вообще причем? Не используй, никто не заставляет. Если что иммутабельности можно добиться и без final полей.

maloi ★★★★★ ()
Ответ на: комментарий от maloi

мутабельность и многопоточность тут вообще причем?

Твой код используется только в студенческих лабах?

Если что иммутабельности можно добиться и без final полей.

При чем тут final поля если у тебя объект публикует часть своего «приватного» состояния и позволяет его менять?

Deleted ()
Ответ на: комментарий от Deleted

При чем тут final поля если у тебя объект публикует часть своего «приватного» состояния и позволяет его менять?

ЩИТО?

maloi ★★★★★ ()
Ответ на: комментарий от maloi

Начинаем с азов. Знаешь что такое инкапсуляция?

Deleted ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.