Например, подготовку контейнера, в который кладётся 100500 данных (включая погоду на марсе), я бы вынес в отдельную функцию.
Это и вынесено в отдельную функцию. BuildTop сама ничего помимо этого не делает. Сама логика вынесена в Method Object. В контейнер кладется не всё (включая погоду на марсе), а только данные в контексте. Этот набор не может быть шире набора параметров метода. Собственно на параметры метода интерфейса и навешиваются аттрибуты.
А ещё можно полунастроенные контейнеры передавать в функции, чтобы повторно не настраивать для множества вызовов с одинаковыми доп. параметрами. В таких сценариях метод, вызывающий сервис, не будет содержать в своём теле вызовы RegisterInstance.
Такая проблема есть.
В классе который должен инжектировать данные в контейнер можно забыть инжектировать данные. На это можно написать валидатор, который будет проверять наличие в теле метода вызова Register.
Доставать же из контейнера данные если они не нужны конкретной реализации собственно и не нужно.
Компонент, который получает в качестве параметра контекст только потому что компоненту, который он использует, необходимо передать контекст от компонента, который пользуется этим. Я считаю, что это ужасно.
В такой ситуации я предпочитаю поступать описанным способом. Возможно, это моя ошибка. Но я ещё ни разу об этом не пожалел, скорее наоборот.
Я думаю, что мы прекрасно поняли позиции друг друга, это хорошо, а я не претендую на истину в последней инстанции :-).
Ну и да, какой с этих атрибутов толк? У вас есть статическая проверка того, что при любой активации зависимости эти данные вброшены в контекст
Статической быть не может, а динамическую делает сам контейнер. Также как нет статической проверки корректности конфигурации контейнера, но есть статическая проверка корректности вызова конструктора через new.
А вообще — двусторонняя связанность, потребитель знает о зависимости, а зависимость знает о том, что потребитель в нее вбросит. Как раз то, чего IoC пытается избежать.
Я знаю, что это плохо. Но передача контекста параметром с этой точки зрения не лучше. Одному компоненту прийдется знать что надо передавать контекст, а другому его получать. Этого не избежать.
Чего я избежал — так это необходимости сообщать об этой зависимости всем посредникам между ITopBuilder и IRanker. К таковым может относиться композит из всех реализаций IRanker.
Ну то есть вы все равно где-то явно определили этот набор данных. Причем в потребителе зависимости (т.е., том месте, о котором зависимость знать не должна).
Да. Методы интерфейсов, выполнение которых подразумевает инжектирование данных (BuildTop) помечаеются аттрибутами говорящими «Я инжектирую это, это и это».
Интерфейсы компонентов, реализация которых подразумевает разрешение данных из контейнера (IRanker) помечаются аттрибутом «Я ожидаю данные этого, этого и этого типов из контейнера».
Тому, кто реализует IRanker не нужно знать кто эти данные инжектирует, но это всегда можно узнать по парным аттрибутам.
Нет. Только то что передано в качастве параметров методоу BuildTop
Каким контрактом?
Описанием метода BuildTop и соответствующими аттрибуттами на его параметрах, которые я, к сожалению, оставил, за рамками поста.
Юнит-тесты тут вообще не при чем, корректность вбрасывания проверяется интеграционными.
Корректность передачи контекста тоже интеграционными?
Я говорю про тесты на способность реализации метода BuildTop выполнять его функцию. Если теперь, из-за использования Data Injection они стали интеграционными, то ок.
По факту, это означает, что вы используете обычный контекст, просто извращенным способом.
Смысл называть это отдельным шаблоном?
Этот шаблон решает ту же проблему что и контекст, но на мой взгляд, делает это удобнее. Но решает он её иначе. Поэтому это отдельный шаблон.
Вот только вам надо не забыть вбросить данные в контейнер (не имея никакой конкретной информации о том, какие именно данные нужны потребителям)
Поэтому вбрасываются все данные которые есть. А потребители сами разбирутся, что им нужно. Благо список всего, что им можно получить четко обозначен контрактом.
при этом не забыть породить все потребители именно в этом контейнере.
Об этом трудно забыть. Т.к. компонент в этом случае не заработает в принципе, даже если данные не нужны никому. И не пройдет ни один юнит тест.
По сути, вы используете дочерний контейнер как контекст-бэг.
Да. Одновременно с этим ограничивая время жизни зависящих от контекста компонентов временем жизни дочернего контейнера (не больше), что позволяет использовать контексто-зависимые техники кэширования и т.п.
Но сразу после того как я инжектирую данные в контейнер я могу забыть об этом и, при использовании любого конктерного сконструированного IRanker, не думать более об этом при каждом вызове.
Более того, я поделил обязанности по передаче констекста и собственно ранжированию между разными классами.
Да.
С точки зрения абстракции мне удобнее не знать, какие именно данные нужны конкретному IRanker чтобы выставить оценку. Это не имеет для алгоритма ранжирования никакого значения. Зачем же я должен передавать в качестве параместра всё что может понадобиться какой-то из реализацй, но скорее всего не понадобится?
Не для сокрытия ли этого как раз и нужен IoC?
Я всегда думал, что смысл DI не в конструировании объектов. С этим прекрасно справляются обычные фабрики. Я всегда думал, что DI нужен для того, чтобы позволить компоненту ничего не знать про то, от чего зависит компопент, от которого он сам зависит (я говорю про зависимости зависимостей).
Это аналогично принципу «вассал моего вассала — не мой вассал» и сильно упрощает жизнь.
Делегирование конструирования объектов на контейнер, это то, чем мы платим (да это цена, ибо это не очевидно) за избавление от проблемы зависимостей зависимостей, и многих других.
Они, так же как и плюсы Dependency Injection становятся отчетливее, когда зависимостей по данным становится очень много.
Передача лишнего параметра в данном случае видится мне аналогией конструирования объекта через new с передачей параметра. Это нормально, до тех пор, пока мы не ловим себя на том, что нам приходится разрешать параметры параметров и параметры параметров параметров, чтобы передать их в конструктор.
Когда-то при чтении кода вы впервые столкнулись с Dependency Injection, и, скорее всего, впервые столкнувшись с ним, вы на мгновение подумали, что это не просто и не очевидно.
Да, при кривой конфигурации в самом деле нет никакого выигрыша. Но в этом случае думать в первую очередь надо о том, что в проекте ещё кривое, кроме конфигурации. (Тот факт, что выигрыша не будет, если кто-то где-то ошибется не означает что его нет совсем и быть не может.)
А это (доступ ко внешним данным) кому-то нужно? А вот прозрачность системы вы резко понизили, что на архитектуре сказывается отрицательно.
Если доступа ко внешним данным никому не нужно, то я не рекомендую применять описаный шаблон. Даже запрещаю это делать.
Если же всё-таки он нужен, то компонент который инжектирует данные должен быть соответствующем образом отмечен, как и компонент, реализация которого допускает импорт данных из контейнера c явным указанием всех таких типов данных. Например, аттрибутами.
Это и вынесено в отдельную функцию.
BuildTop
сама ничего помимо этого не делает. Сама логика вынесена в Method Object. В контейнер кладется не всё (включая погоду на марсе), а только данные в контексте. Этот набор не может быть шире набора параметров метода. Собственно на параметры метода интерфейса и навешиваются аттрибуты.Ничего не понял. Нужен пример.
В классе который должен инжектировать данные в контейнер можно забыть инжектировать данные. На это можно написать валидатор, который будет проверять наличие в теле метода вызова Register.
Доставать же из контейнера данные если они не нужны конкретной реализации собственно и не нужно.
В такой ситуации я предпочитаю поступать описанным способом. Возможно, это моя ошибка. Но я ещё ни разу об этом не пожалел, скорее наоборот.
Я думаю, что мы прекрасно поняли позиции друг друга, это хорошо, а я не претендую на истину в последней инстанции :-).
А ещё все знают о том, что все знают о контексте, и что его надо всем передавать…
Это способ документирования кода. Эти аттрибуты нужны в качестве спецификации для разработчика.
Кстати, на этот счет я погорячился. Её можно сделать. Но пока её нет :-)
Статической быть не может, а динамическую делает сам контейнер. Также как нет статической проверки корректности конфигурации контейнера, но есть статическая проверка корректности вызова конструктора через new.
Я знаю, что это плохо. Но передача контекста параметром с этой точки зрения не лучше. Одному компоненту прийдется знать что надо передавать контекст, а другому его получать. Этого не избежать.
Чего я избежал — так это необходимости сообщать об этой зависимости всем посредникам между
ITopBuilder
иIRanker
. К таковым может относиться композит из всех реализацийIRanker
.Да. Методы интерфейсов, выполнение которых подразумевает инжектирование данных (
BuildTop
) помечаеются аттрибутами говорящими «Я инжектирую это, это и это».Интерфейсы компонентов, реализация которых подразумевает разрешение данных из контейнера (
IRanker
) помечаются аттрибутом «Я ожидаю данные этого, этого и этого типов из контейнера».Тому, кто реализует
IRanker
не нужно знать кто эти данные инжектирует, но это всегда можно узнать по парным аттрибутам.Нет. Только то что передано в качастве параметров методоу
BuildTop
Описанием метода BuildTop и соответствующими аттрибуттами на его параметрах, которые я, к сожалению, оставил, за рамками поста.
Корректность передачи контекста тоже интеграционными?
Я говорю про тесты на способность реализации метода BuildTop выполнять его функцию. Если теперь, из-за использования Data Injection они стали интеграционными, то ок.
Этот шаблон решает ту же проблему что и контекст, но на мой взгляд, делает это удобнее. Но решает он её иначе. Поэтому это отдельный шаблон.
Поэтому вбрасываются все данные которые есть. А потребители сами разбирутся, что им нужно. Благо список всего, что им можно получить четко обозначен контрактом.
Об этом трудно забыть. Т.к. компонент в этом случае не заработает в принципе, даже если данные не нужны никому. И не пройдет ни один юнит тест.
Да. Одновременно с этим ограничивая время жизни зависящих от контекста компонентов временем жизни дочернего контейнера (не больше), что позволяет использовать контексто-зависимые техники кэширования и т.п.
IRanker
, не думать более об этом при каждом вызове.Более того, я поделил обязанности по передаче констекста и собственно ранжированию между разными классами.
Когда операция становится частью чего-то большего появляется контекст контекста, а потом контекст контекста контекста.
Да. Но мы хотим зависеть только от обстракции.
Я данные
не передаю
. Япозволяю
любой конкретной реализации получить эти данные,если ей это нужно
. Происходит инверсия управления.С точки зрения абстракции мне удобнее не знать, какие именно данные нужны конкретному
IRanker
чтобы выставить оценку. Это не имеет для алгоритма ранжирования никакого значения. Зачем же я должен передавать в качестве параместра всё что может понадобиться какой-то из реализацй, но скорее всего не понадобится?Не для сокрытия ли этого как раз и нужен IoC?
Это аналогично принципу «вассал моего вассала — не мой вассал» и сильно упрощает жизнь.
Делегирование конструирования объектов на контейнер, это то, чем мы платим (да это цена, ибо это не очевидно) за избавление от проблемы зависимостей зависимостей, и многих других.
Передача лишнего параметра в данном случае видится мне аналогией конструирования объекта через new с передачей параметра. Это нормально, до тех пор, пока мы не ловим себя на том, что нам приходится разрешать параметры параметров и параметры параметров параметров, чтобы передать их в конструктор.
Да, при кривой конфигурации в самом деле нет никакого выигрыша. Но в этом случае думать в первую очередь надо о том, что в проекте ещё кривое, кроме конфигурации. (Тот факт, что выигрыша не будет, если кто-то где-то ошибется не означает что его нет совсем и быть не может.)
Если доступа ко внешним данным никому не нужно, то я не рекомендую применять описаный шаблон. Даже запрещаю это делать.
Если же всё-таки он нужен, то компонент который инжектирует данные должен быть соответствующем образом отмечен, как и компонент, реализация которого допускает импорт данных из контейнера c явным указанием всех таких типов данных. Например, аттрибутами.