Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
Не нужны для бизнес-логики.
МП>Они затрудняют распутывание кода, МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>и это не перекрывается гибкостью подстановки mock-объектов
Распутывание кода и рефакторинг в основном затрудняет тотальное использование интерфейсов, которые так любят использовать в DI фреймворках.
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
Тут такое дело. Сам по себе DI требует некоторого осмысления. В доисторические времена мы пёрлись от понимания таких вещей как косвенная адресация в ассемблере. Сегодня эту нишу занял DI. А осмыслив эту штуку народ переходит на следующий уровень, где DI превращается в золотой молоток — уверенность в полной универсальности данного инструмента.
Ещё одним усугубляющим проблему фактором является то, что большинство программистов не понимают разницы между написанием таких вещей как System.String и MyCompamy.CustomerService.GetCustomerByID. Не понимают того, что для этих вещей нефункциональные требования абсолютно разные.
Первое — это универсальный код, предназначенный для многократного повторного использования.
Второе — бизнес логика, которая за время своего жизненного цикла будет многократно меняться.
Проблема и непонимание заключается в том, что бизнес логика, которая будет многократно менятся, пишется как универсальный код, предназначенный для многократного повторного использования. Мы же все здесь минимум архитекторы, так ведь? Любой код сразу пишем как всеоблемющий всемогутор. А раз так, то как же можно получить информацию о кастомере без DI фреймворка и пары IoC контейнеров? Никак не можно.
Ещё один момент. Написание повторно используемых компонентов — это тоже скил, который нужно прокачивать. Без этого получаются не повторно используемые компоненты, а кособокие уродцы.
В результате имеем — неумеха с золотым молотком пишет со всем старанием бизнес логику в стиле универсального всемогутера.
Какждый раз когда я вижу в проекте использование DI фреймворков и IoC контейнеров у меня возникакет именно такое впечатление.
Если нам не помогут, то мы тоже никого не пощадим.
С моей профессиональной точки зрения DI фреймворки не нужны.
Они затрудняют распутывание кода,
понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
и это не перекрывается гибкостью подстановки mock-объектов
но обнаруживаю ярых адептов этого всего.
уже и в вакансиях суют такое требование
кто-то может популярно расписать преимущества либо природу явления популярности?
(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Somescout, Вы писали:
S>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI?
Отличное описание. DI это костыль, когда лень думать об архитектуре, но не лень потом все это разгребать и отлаживать.
Здравствуйте, Somescout, Вы писали: TG>>И если сделано это осознанно, то было бы опять же интересно посмотреть на конкретный кейс. S>Ок, простой пример: на веб странице компонент должен выводить имя текущего пользователя. Он должен его откуда-то получить: либо вы при рендеринге каждой страницы прокидываете эту информацию ему вручную, либо компонент так или иначе (через DI, репозиторий, синглтон) получает эту информацию сам.
Ну это же прекрасный пример. Через три-четыре релиза у нас в топ жалоб выезжает "ваше приложение нещадно тормозит", привлекается команда высокооплачиваемых экспертов, которые после трёх месяцев расчистки конюшен констатирует очевидное: "у вас там каждая кнопка в гуе считает, что ей нужно лазить в базу/сторонний сервис/ещё куда-то, в итоге для построения одного экрана открывается 27 коннектов к базе, и исполняется более трёхсот SQL-запросов. Из них треть дублируют друг друга, а половина, хоть и не совпадает, может быть покрыта одним из более широких запросов". Рекомендация экспертов: "чётко разделить фазы построения гуя, доставая данные из базы минимальным количеством запросов и передавая визуальным компонентам готовую viewModel".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, gandjustas, Вы писали:
МП>>но обнаруживаю ярых адептов этого всего. МП>>уже и в вакансиях суют такое требование G>Уже? Доброе утро. Погугли что означает D в SOLID
Попробуй сам погуглить, что же именно означает D в SOLID
Здравствуйте, Министр Промышленности, Вы писали:
МП>
МП> var userDao = DatabaseHelper.GetUserDao();
МП> var authService = new AuthService(userDao);
МП>
Поздравляю, вы только что показали пример использования паттерна Dependency Injection.
А фреймворк даст возможность не писать эти две строчки. Которые разрастутся в сотни и тысячи строк однотипного скучного кода в реальном приложении с десятками-сотнями объектов. Если писать это вручную, нужно будет самому продумывать порядок создания объектов, причём этот порядок с развитием приложения может меняться. В этом нет ничего сложного, особенно если пользоваться setter-ами, а не конструкторами (тогда можно не задумываться про порядок создания объектов и круговые зависимости), но этот код легко генерируется, поэтому и пользуются фреймворками. Но вы можете не пользоваться и писать всё руками, DI от этого не исчезнет.
Здравствуйте, Somescout, Вы писали: S>Про настройку времени жизни в DI и request-scope я так понимаю вы ни сном ни духом?
Нет, почему же. Просто приведённый пример не может служить аргументом в пользу DI-контейнеров. То, что в недрах гуя нет простого доступа к DBConnection — это хорошо, а не плохо. Так что нам не нужно средство, которое даёт эту невыносимую лёгкость сделать "а шандарахну-ка я тут запросец в базу".
А вообще, в целом, я согласен с теми из участников дискуссии, которые считают развитые DI-контейнеры всего лишь средством переноса сложности из исходного кода в конфигурацию.
Может быть, конечно, я преувеличиваю масштаб проблемы, но я в своё время нахлебался с системами, построенными по "чрезмерно компонентному" принципу. Типа "у нас есть набор универсальных кубиков, и мы в рантайме собираем из них нужное нам приложение". В итоге оказывается, что насквозь процедурный код инициализации зависимостей прекрасно отлаживается, рефакторится, и версионируется штатными средствами; а все чудеса "внешней конфигурации" — нет. Просто в рантайме почему-то в конкретный компонент уезжает не тот же самый DBConnection, что и у всех (или, наоборот, тот же самый, вместо нужного отдельного), или ещё какая пежня творится. А понять, кто в какой момент что там оверрайдит, и откуда взялись вот эти вот параметры конструктора — упражнение не для слабонервных.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
AK>>При чём здесь строковые константы? Я не понимаю. AK>>Можете дать какой-нибудь пример?
S>Я похоже переврал, что конкретно можно делать только resolve по имени, что ослабляло бы типизацию. S>Но вот дискуссия на SO, которую я считаю релевантной -- https://stackoverflow.com/a/4988337/241446 S>В частности вот это: S>
S>Errors are pushed to run-time. If you configure your Guice module incorrectly (circular reference, bad binding, ...) most of the errors are not uncovered during compile-time. Instead, the errors are exposed when the program is actually run.
S>Т.е. из-за DI некоторые ошибки будут заметны на стадии исполнения, нежели при компиляции. Вероятно это ТС имел в виду.
я имел в виду банальную задачу:
есть фарш исторического кода, который неправильно работает
ты разбираешься в какой момент всё идёт неправильно, отслеживаешь объекты где используются, откуда получаются
и вот на 7м-13м уровне вложенности вызывов ты понимаешь, тебе надо срочно знать откуда берётся значение вот в этом объекте
прыг F12 (у меня это решарперовский GoToDefinition)
ага есть конструктор прыг Alt+F7 (у меня это FindUsages) а хрен тебе — он вызывается неявно в рамках DI
и хорошо если ты это ещё знаешь
а то вполне можешь подумать "ага, значит инициализируется не здесь", и пойти исследовать фарш дальше вглубь!
и это убийственно
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
Надо было писать "недостаточно профессинальной"
МП>Они затрудняют распутывание кода, МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>и это не перекрывается гибкостью подстановки mock-объектов
DI нужен в первую очередь для фреймворков, а не бизнес-логики.
МП>но обнаруживаю ярых адептов этого всего. МП>уже и в вакансиях суют такое требование
Уже? Доброе утро. Погугли что означает D в SOLID
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
Давай сразу возьмем уданчый пример. ASP.NET Core например, он построен на DI.
В любом классе, который получается через DI можно написать так:
public MyClass
{
public MyClass(ILogger<MyClass> logger) { ... }
}
Тебе нигде не надо явно создавать логгер, знать его тип или знать что-то о его времени жизни.
Попробуй полчить аналогичные возможности без DI.
Более того, настроив DI, ты дальше можешь написать так:
public MyOtherClass
{
public MyOtherClass(MyClass x) { ... }
}
При этом можешь сделать MyClass как синглтоном, так и создавать экземпляр на каждый запрос.
САД>тебе известны такие понятия как SOLID, и, конкретно, D из этой группы? или что такое DI как паттерн и для чего он вообще нужен?
Выше уже обратили внимание, что D. Injection D. Inversion не одно и тоже. В SOLID речь идет об Inversion,
для этого Injection можно не использовать, а просто придерживаться соотв. принципов разработки и проектирования.
Достигается легко -- везде и всюду использовать интерфейсы или базовые классы.
Контейнер же вообще не требует от программиста написания оператора new в явном виде, кроме всяческих
абстрактных фабрик и не более.
Конструктор. Если мы не передаем что-то explicitly, мы просто передаем это что-то implicitly, сбоку и невидимым путем. Через реестр в DI container, например.
Простыми словами: у нас есть реестр (глобальных в пределах одного DI container) статических переменных. Например, в XML-файле, bean definitions.
Скрытый текст
Можно написать так:
logger = new MyLoggerTypeInstance().
Можно так:
logger = MyLoggerFactory::createInstance().
А можно сделать вид, что мы никогда не конструируем объект императивно, и как будто бы некий DI container будет конструировать объект за нас. Но реальность такова, что кто-то же должен сконструировать сам di container, и заодно задать все зависимости между объектами в контейнере.
И в развернутом виде будет создание объекта будет выглядеть так:
logger = new Logger(someGlobalDIContainer::readConfigFor(Logger)).
Что в итоге? Ровно те же зависимости. Только теперь записанные в "конфигурации контейнера". На другом языке (XML, например). Превосходная иллюстрация на тему "configuration complexity clock"
И XML это всего лишь третья стадия. А когда мы начинаем вместо XML писать "генераторы XML" (уверен, что кто сталкивался со "зрелыми" наслоениями мамонтов, обязательно на что-то такое натыкался), — вот это веселуха. Писать Java-код, который читает XML, который сгенерирован кодом на, скажем, Python. Получили сложность на ровном месте.
Здравствуйте, Министр Промышленности, Вы писали:
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI? Ок, я могу создать её просто конструктором, но нужна строка подключения. Которая задана в конфигурации. Которая взята из файла/реестра/сервера. Следовательно чтобы просто создать контекст, мне нужно сначала прочитать конфигурацию, получить из неё строку подключения, а затем уже подключиться к базе. Выглядит геморройно (а уж сколько веселья при рефракторинге, если потребуется поменять способ построения контекста).
А можно вместо этого просто прописать в конструкторе требование к наличию этого объекта — и всё, DI автоматически создаст его без каких-либо телодвижений.
И это даже не касаясь вопроса о scope'ах — какой контекст мне нужен transient или scoped? И как в принципе создать второй без DI?
В общем узнать в своё время о существовании DI было крайне приятно.
Здравствуйте, Ночной Смотрящий, Вы писали: НС>Зачем для этого граф? DI просто заменяет явный вызов ресолва сервиса на неявный, ориентируясь на сигнатуру ктора, методов или набор свойств. Каким боком тут нужен граф зависимостей? Ты этот вопрос уже три раза проигнорировал. Если нет ответа — так и скажи.
Ну давайте напишем вот так:
public BankingService: IBankingService
{
public BankingService(IDeliveryService deliveryService){...}
...
}
...
public DeliveryService: IDeliveryService
{
public DeliveryService(IBankingService) {...}
...
}
Когда мы делаем "ручной DI", в какой-то момент программист вынужден написать вызовы new DeliveryService() и new BankingService(). Компилятор бъёт его по рукам, потому что получившийся DAG не сводится к дереву.
Причём неважно, где именно написан вот этот вот new — например, у нас может быть тот самый if(Configuration.Value["Delivery"] == "Active") {deliveryService = new DeliveryService(...)}. Компилятор по-прежнему проверяет definiteAssignment и non-null reference вместе с прочими ограничениями архитектуры.
Придётся почесать голову, и эту проблему как-то решить, не показывая промежуточные ошибочные стадии решения другим контрибуторам.
А когда мы полагаемся на "уолшебный контейнер", то обнаружение кольцевой зависимости откладывается до рантайма. Поэтому такой код можно прекрасно закоммитить в репозиторий; и он даже может ездить в продакшне — до тех пор, пока кто-то не попытается активировать сервис доставки при помощи конфигурации.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>>>К чему эта софистика? НС>·>Это ключевое слово: Inversion of Control. НС>Нет такого ключевого слова в определении service locator. Ты упорно пытаешься смешать две разные вещи.
Like Dependency Injection, the Service Locator Pattern allows dependencies to be defined externally and apart from the application logic. But rather than injecting, these dependencies are made available upon request by the application.
и т.д. https://www.c2experience.com/blog/inversion-of-control-using-an-injectable-service-locator
НС>>>·> Как используя DI можно что-то получить? НС>>>Объявить параметр в конструкторе. НС>·>Параметр объявляет тип, а инжектится инстанс. НС>И? При вызове Resolve указывается тип, а возвращает он инстанс.
Типы тут вообще непричём. Можно делать даже так если нет генериков:
НС>·> И соотвественно в случае SL надо вызывать Resolve для того чтобы получить инстанс для данного типа. НС>Нет такого в определении service locator.
"...on request returns the information necessary..."
НС>·>В динамических языках, где все типы "object" НС>При чем тут динамические языки?
Притом что типы к DI/SL прямого отношения не имеют.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Эйнсток Файр, Вы писали:
ЭФ>Ты просто старый.
ЭФ>Раньше такая же проблема с темплейтами в C++ была, ЭФ>не все понимали зачем ими пользоваться, если можно и без них.
DI это довольно старый паттерн. Если ТС не просидел 20 лет на необитаемом острове, он бы знал.
Здравствуйте, varenikAA, Вы писали:
AA>// Что тут неявного?
Непонятно кто от кого и как зависит.
AA>Зато точка сборки приложения одна а не размазана по разным сборкам.
Просто делай то же самое, но без контейнера. Если я правильно разгадал твой код:
var dbOptions = new DbOptions()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
var dbContext = new DbContext(dbOptions);
var mainService = new MainService(dbContext);
services.AddHostedService(mainService);
Код внезапно стал проще — никаких лямбд, генериков, рефлексии, даункастов. Можно использовать IDE вовсю — find usages, declarations, использовать рефакторинги.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
МП>уже со времён WCF сервисов заметил, что сложность конфигурирования зачастую превышает сложность кодирования
Потому что конфигурирование и есть кодирование.
Более того, "конфигурирование" требует ровно того же тестирования, что и "кодирование", и ровно такого же процесса выкатывания изменений.
Просто многие (порой даже как бы "сеньоры") этого не понимают, и думают, что если они назовут код "конфигурацией", то снова можено будет "як-як, и в продакшн". А то ведь, ишь, понапридумают — test coverage, unit testing, CI, — это ж надо понимать, что делаешь, как работает. А то еще и вовсе, о ужас, написать новый тест, да не просто тест, а такой, который убеждается, что в процессе "смены конфигурации" ничего не сломается.
Ведь куда проще же назвать это "конфигурационным флагом", и не тестировать. Зато потом следующий инженер, которому досталось наслоение таких "конфигурационных авгиевых конюшен", четко определит карму своего предшественника.
PS: написанное выше относится только к слову "конфигурация". Но не с dependency injection. Эта идиома по сути своей представляет не что иное как способ задавать интерфейс (API). Почему придумали новый термин... ну, сразу вот это вспоминается:
Здравствуйте, petroa, Вы писали:
P>Это ведь вроде, так или иначе, придётся делать самому. Можно делегировать ioc-контейнеру. Интересно ваше мнение.
Дело не в конфигурации. Обрати внимание, нелюбители DI фреймворков сетуют прежде всего на то, что с таким кодом сложно разбираться. Это действительно так. Это сложно понять, если ты написал пару проектов исключительно как писатель и не занимался их поддержкой продолжительное время. А ещё лучше это прочувствовать на чужом коде. Вот представь, у тебя есть простейший код:
class Foo
{
public Foo(Bar b)
{
if (!b.IsValid)
throw ...
}
}
Код кидает исключение. Твоя задача — найти причину. Если код без DI фрейворков, то эта задача из разряда примитивных. Shift-F12 в студии (R#?) покажет тебе абсолютно все вызовы конструктора и далее по цепочке можно раскрутить всё даже без необходимости запускать этот код. Если же используется DI фреймворк, то для того, чтобы понять что происходит необходимо запускать как минимум отладчик. При этом возникает несколько проблем.
— этот код может находится в трудно доступном месте и для его выполнения потребуется создание определённого сценария, подготовки окружения и набора данных.
— проблема может возникать только в определённом окружении недоступном для отладки, а при создании искуственных условий проблема не воспроизводится.
При наихудшем сценарии проблему всё равно придётся решать путём анализа кода, но в случае с DI фреймворком это на порядок сложнее.
И здесь я опять вынужден повторить свой вопрос. Какую проблему решают DI фреймворки, которая по своей значимости способна перевесить вносимые в проект неудобства?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Буравчик, Вы писали:
S>>А в чем отличие? Б>Из минусов — больше wiring-кода,
Это как правило неправда. В лучшем случае кода будет примерно столько же. А чаще кода будет меньше, т.к. теперь не надо описывать тот же wiring-код в виде api данного контейнера — всякие там аннотации, специальные интерфейсы, конфиги, регистрация компонент и т.п.
Кода меньше может быть если использовать неявную регистрацию компонентов, что в больших проектах очень не рекомендуется. А в простых проектах это будет экономия на спичках.
Б> сложнее управлять сроком жизни объектов.
Это тоже не совсем правда. Тут ещё одна путаница. Тут уже путаются понятия "IoC-контейнер" и фреймворк. Контейнер это просто свалка инстансов, мапа просто {тип -> объект}. А фреймворк это уже целая интегрированная библиотека с кучей готовых тулзов. Например, AOP, lifecycle (start/stop/Disposable), context events и т.п.
Ещё могут быть готовые к использованию чтение конфигов, scopes, маппинги для http и т.п. И это уже всё к IoC не относится.
Б> Чуть менее удобно переопределять зависимости для тестирования — приходится создавать наследника для переопределения методов.
Это ерунда какая-то, к DI прямого отношения не имеет.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Министр Промышленности, Вы писали:
МП>все аргументы которые ему нужны он попросит как входящие аргументы МП>причем эти все аргументы наверняка будут примитивными типами, типа строк и чисел
Это приведет к тому, что ты будешь передавать параметры, которые нужны нижестоящим сервисам, а также еще нижестоящим и т.п.
Таких параметров будет очень много (вся конфигурация приложения). А самое главное, при добавлении нового свойства конфигурации тебе придется менять конструкторы всех классов, которые "пропускают" через себя этот параметр. Аналогично при изменении какого-то компонента системы — если ты изменил реализацию (заменил БД на микросервис), то свойства станут другие, и опять же придется переделывать кучу кода по созданию объектов.
А в проектах с DI ты делаешь изменение в одном месте — в composition root.
МП>чтобы его вызвать нужно будет вначале явно задействовать логику по прочтению конфигурации/обращению к базе данных и т.п. для получения значений
Это вообще ужас. Мало того, что нужно передавать 100 параметров, так еще каждый раз когда мы вызываем этот хелпер, мы должны каким-то способом обратиться к БД.
Но для работы с БД, нам нужна connection string и прочие параметры БД. Откуда мы возьмем их? Опять вызовем хелпер, чтобы вызвать другой хелпер? В общем, получается лапша хелперов.
Чтобы убрать эту лапшу поступают просто — объекты делают глобальными (конфигурация, контекст БД и т.п.). И рано или поздно проявляются проблемы работы с глобальными переменными.
МП>по мне так это структура программы здорового человека МП>она будет явной, поддерживаемой и развиваемой
Имхо наоборот, DI позволяет разбить всю систему на абсолютно независимые блоки, а затем из небольших блоков иерархически собирать всё более крупные блоки. Позволяет легко создавать и тестировать блоки — они относительно небольшие и независимые, позволяет легко заменять и добавлять блоки. Позволяет гибко настраивать конфигурацию всего приложения и каждого блока в отдельности.
Именно эта независимость частей системы придает системе гибкость, и это же добавляет сложности. Просто с ней работать нужно немного иначе.
Здравствуйте, Sharov, Вы писали:
S>А в чем отличие?
DI можно использовать и без DI-контейнера. Весь код по созданию объектов пишется вручную — как классы с методами createAnything (в Main или в какой-то подсистеме).
Из плюсов — явный вызов всех конструкторов. Удобно описываются взаимосвязи, а также все связи контролируются компилятором (для статических языков).
Это как раз тот случай, когда и DI используется, и все взаимосвязи указаны явно (как хочет топик-стартер) — можно использовать Find Usage и прочие штуки.
Из минусов — больше wiring-кода, сложнее управлять сроком жизни объектов. Чуть менее удобно переопределять зависимости для тестирования — приходится создавать наследника для переопределения методов.
Здравствуйте, Sinclair, Вы писали:
S>Компилятор бъёт его по рукам, потому что получившийся DAG не сводится к дереву.
Чуть поправлю: "получившийся граф не сводится к DAG, т.к. содержит цикл". Деревья тут непричём.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, SkyDance, Вы писали:
SD>Выше по теме уже выразили мнение, с которым я особенно согласен: лучший код — тот, который сразу понятно что делает. А это, в свою очередь, такой код, где минимум уровней абстракции, через которых нужно продраться.
Абстракции позволяют создавать нам сложные системы. Через абстракции не надо продираться, их надо принять (понять), не залезая внутрь. На то они и абстракции.
Про DI добавлю, что когда используешь DI тебе приходится разделять код на независимые компоненты. Начинаешь мыслить компонентами, что-ли.
А если "просто писать" код не задумываясь об этом, то код получается связанным, и становится еще труднее понять, что и как работает.
МП>>>>нормальные обычные конструкторы, МП>>>>ну либо статические конструкторы,
МП>>вопрос был в разрезе целесообразности использования DI
T>до этого момента, когда ты спрашивал про DI-фреймворки, всё было правильно, T>а сейчас съехал в сторону T>передача зависимостей в конструкторе это такое же средство реализации DI
да, но без шва
всё ясно понятно как именно откуда берётся в том же месте где ты это раскопал
а не в другом отдельном
а то и вообще не найдёшь поиском кто конструктор вызывает
зачем ещё и тянуть в проект дополнительные DI фреймворки при этом...
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
M>>>>покажите пример, а мы посмотрим. МП>>>это не вполне легально по нынешним временам МП>>>но всё что я сам писал было без DI кроме мест в проектах где это было уже мне навязано через легаси
M>>Не обязательно весь проект, просто пример как вы пишите классы решающие бизнес задачи, как передаете в них зависимости?
МП>это всё решается грамотной иерархией сборок по слоям МП>есть уровень простейших объектов, data-object МП>сборки этого уровня никуда не ссылаются МП>есть уровень helper'ов, DAL и технической логики МП>сборки этого уровня могут ссылаться на сборки предыдущего уровня МП>и есть уровень содержательной бизнес логики, который уже может ссылаться на все предыдущие уровни, но по необходимому минимуму
МП>циклических зависимостей тут в идеале вообще возникнуть не должно МП>но если возникают, вы же знаете как их решать? МП>(создание интерфейса нужной функциональности, вынос его в нижестоящий слой, а реализация может быть и в вышестоящем слое)
Пока что вы описали чистую архитектуру, а я спрашивал не как вы организуете слои, а как передаете зависимости в конкретные классы.
M>>В каком месте приложения конструируете зависимости?
МП>я их не конструирую, а избегаю МП>но речь я так понял о конструирования высокоуровневых объектов? МП>ну так там и конструирую где они нужны МП>либо если это кеш — при инициализации или обновлении кэша
МП>
МП> var a = new A(12, "6")
МП> {
МП> Field1 = new B(),
МП> Field2 = C.Instance;
МП> }
МП>
M>>Зачем делаете это руками если есть контейнер который справится с этим сам и без проблем?
МП>с чем это он справится сам? МП>он явные аргументы конструирования перенесёт в отдельное место (совершенно неявное) МП>чем создаст шов в восприятии МП>а то и вообще в конфиг файл
МП>но суть работы по инициализации всё равно нужно будет делать
Например есть у нас некий полезный класс у которого две зависимости:
class Service
{
public Service(IDependency1 dependency1, IDependency2 dependency2)
{
}
public void DoSomeWork()
{
//...
}
}
Как вы предлагаете создавать экземпляр этого класса? Руками писать new Service(new Dependency1(...), new Dependency2(...)), при чем у каждой зависимости есть свой конструктор с другими зависимостями. А если потом в этой цепочке где то поменяется сигнатура конструктора — придется исправлять все места где он вызывается. Вопрос зачем такие сложности, если задачу создания объектов можно поручить фреймворку который сделает это все совершенно прозрачно?
Здравствуйте, Sinclair, Вы писали:
S>>Про настройку времени жизни в DI и request-scope я так понимаю вы ни сном ни духом? S>Нет, почему же.
Да потому же — вы говорите про 27 коннектов и 300 запросов, хотя как раз DI позволяет в такой ситуации переиспользовать один контекст с одним соединением и кэшированием запросов.
S>Просто приведённый пример не может служить аргументом в пользу DI-контейнеров. То, что в недрах гуя нет простого доступа к DBConnection — это хорошо, а не плохо. Так что нам не нужно средство, которое даёт эту невыносимую лёгкость сделать "а шандарахну-ка я тут запросец в базу".
Ок, а если в этом месте нужны данные из базы — что именно вы предлагаете с этим делать?
S>А вообще, в целом, я согласен с теми из участников дискуссии, которые считают развитые DI-контейнеры всего лишь средством переноса сложности из исходного кода в конфигурацию.
У меня складывается впечатление что некоторые участники дискуссии слегка побаиваются DI.
S>Может быть, конечно, я преувеличиваю масштаб проблемы,
Нет, вы докопались до DbContext(знал бы что вас на нём заклинит — написал бы DAO или "Сервис получения данных (с) (tm)")
S>но я в своё время нахлебался с системами, построенными по "чрезмерно компонентному" принципу. Типа "у нас есть набор универсальных кубиков, и мы в рантайме собираем из них нужное нам приложение". В итоге оказывается, что насквозь процедурный код инициализации зависимостей прекрасно отлаживается, рефакторится, и версионируется штатными средствами; а все чудеса "внешней конфигурации" — нет. Просто в рантайме почему-то в конкретный компонент уезжает не тот же самый DBConnection, что и у всех (или, наоборот, тот же самый, вместо нужного отдельного), или ещё какая пежня творится. А понять, кто в какой момент что там оверрайдит, и откуда взялись вот эти вот параметры конструктора — упражнение не для слабонервных.
То есть с компонентными системами нахлебались, а с процедурными, которые тянут напрямую связи во все зависимые компоненты — нет.
Здравствуйте, Sinclair, Вы писали:
S>Вот вы, скажем, Безумный Макс: Дорога Ярости смотрели? То, что вы описываете, выглядит наоборот: дорога крайне удобная, а вот в конце — всё как в фильме: муть, взрывы, кишки, и прочее.
Чувствую сколь-нибудь конструктивного диалога с вами не выйдет, поэтому просто закончу его на этом.
Здравствуйте, varenikAA, Вы писали:
AA> TG>Вроде, про полезность интерфейсов никто не спорит. AA> TG>Изначальная мысль была: "Просто делай то же самое, но без контейнера." AA> Если разным частям приложения нужны общие объекты, то без контейнера не обойтись, просто это будет велосипед.
Обойтись, конечно. Непонятно, велосипед для чего? Для передачи параметра? Просто передаёшь общий объект разным частям приложения и... собственно всё. Да, вот так всё просто.
AA> Конечно, если это хеловорд, то можно без DI-контейнера, но вопрос как раз о зависимостях как таковых, а не о контейнере который упрощает работу с зависимостями (см тему).
Так продемонстрируй упрощение. Мой фрагмент кода получился проще твоего.
AA>Лично мое мнение, выучи все подходы, а потом забудь все и пиши сообразуясь с ситуацией.
Выше по теме уже выразили мнение, с которым я особенно согласен: лучший код — тот, который сразу понятно что делает. А это, в свою очередь, такой код, где минимум уровней абстракции, через которых нужно продраться.
Здравствуйте, VladD2, Вы писали:
VD>По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей
Мде уж. Это у тебя полная путаница. Основная фича DI — это отделить конструирование компонентов от их использования.
VD>и автоматическое создание объектов в последовательности зависимостей.
А вот это уже называется автоматический DI, осуществляемый неким фреймоворком. Можно делать ручной DI. Я за него и топлю.
VD>Так вот этот процесс можно осуществлять как в рантайме, так и во время компиляции. Последнее дает больше контроля и избавляет от кучи проблем.
Т.е. тебе нужен ручной DI, фреймворки в топку.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, C0s, Вы писали:
C0s>этот пример никак не связан с DI, а напрямую зависит от правильного разделения по слоям и подходу к контекстуализации сессий на нижележащем слое
Еще как связан. Потому что в DI контейнере, если уж ты засунул туда фабрику коннектов к БД, нет никакого способа ограничить его использование. А вот если ты напрямую передаешь коннект, то ты легко можешь отдавать его строго в тот слой, которому можно работать с коннектами к БД.
Здравствуйте, IT, Вы писали:
IT>Документацию см. здесь.
Я вижу у вас она так и осталась таким же г, как и была пару лет назад — фактически Hello World ORM.
IT>Слив защитан.
Да плевать как-то. Я готов дискутировать (и приводить примеры кода) с коллегой, который тоже не чурается приводить примеры своих мыслей в коде. Но извините, вы мне не начальник и не преподаватель чтобы надув щёки что-то от меня требовать. Не хотите сами раскрывать свои мысли — идите лесом. И подальше.
Здравствуйте, varenikAA, Вы писали:
AA>·>Вопрос не про меньше магии, а о смысле магии и где именно эта магия будет находиться. AA>Уже писал, что вопрос изначально хайповый. AA>В чем полезность Poor man's DI?
Вроде уж сто раз обсудили.. Попробую подытожить:
Отделение логики создания компонент от логики их использования и явное описание их взаимосвязей без потери Compile-time проверки зависимостей.
Родная поддержка навигации по коду и рефакторингов в IDE.
Позволяет строго структурировать зависимости и избегать ошибок случайного создания плохих зависимостей.
Живая документация по структуре приложения.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
M>>Нормальное DI как раз и работает через конструкторы. Как вообще можно писать бизнес приложения без DI ума не приложу, покажите пример, а мы посмотрим.
S>Для банковского или шире, финансового ПО, где требования меняются часто, может и критично. Для любых других приложений может и не надо.
в таком описании становится понятно, что оставление DI в проекте есть ни что иное как технических долг...
просто по завершении работ по быстро обновимшимся требованиям забили болт на реформирование структуры классов по сборкам
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, varenikAA, Вы писали:
AA>·>Код внезапно стал проще — никаких лямбд, генериков, рефлексии, даункастов. Можно использовать IDE вовсю — find usages, declarations, использовать рефакторинги. AA>Серьезно? "найти все ссылки" работает прекрасно и в первом случае.
Не работает, например, "где используется данный _инстанс_ dbContext". В первом случае можно поискать только по имени класса, но не отследишь в какой момент конструируется инстанс класса и куда он уходит.
Контейнеры более нормально работают только когда wiring код чисто линейный, каждому типу соответствует ровно один инстанс и если используются какие-то идущие из коробки примочки, навроде request scopes и т.п. Но в этом случае и ручной DI делается левой пяткой, IDE сама всё пишет, даже мозг включать не надо. Зато если чуть шаг в сторону, то сложность контейнеров возрастает на порядок, теряется статический контроль, идёт завязка на специфику конкретного контейнера. В то время как обычный код ручного DI это таки обычный код, который можно модифицировать/рефакторить как любой другой код.
AA>Рефакторинг? Назовите хоть одну проблему.
Первое что в голову приходит — удаление зависимости.
Добавление зависимости — тоже проблема, т.к. до тех пор пока не запустишь, не узнаешь, что новая зависимость действительно есть и доступна.
AA>DI никуда ни делся,
Верно. В том-то и оно. DI — нужен. Контейнер — не нужен.
AA> только добавили кучу не нужных new.
Ещё скажи, что удалили кучу очень важных и полезных AddSingleton, GetService, as, лямбд и прочего.
AA>От интерфейсов м механизма DI все равно никуда спрятаться в клиентском коде, хотя для получения ссылки, хоть для создания локального скоупа,
Интерфейсы оротогональны DI. Можно с ними, можно без них. Обрати внимание, в моём фрагменте кода упоминания интерфейсов нет. И не должно быть, ибо это не забота DI.
AA>либо придется всюду делать ссылку на реализацию. Это отлично работает если у вас развитые средства рефакторинга, но это признак сильных зависимостей.
Нет, не придётся. Интерфейсы нужны там где они нужны и не нужны там где они не нужны. И фреймворк мне тут не указка.
Впрочем, это относится больше к конкретным ЯП. Скажем, в Java можно мокать обычный класс, интерфейсы для тестов не нужны. В c# с этим проблема. Там приходится интерфейсы создавать по поводу и без повода.
AA>Вообще если посмотреть на техники ФП, то там все на интерфейсах(любая функция по сути это он и есть), AA>только за счет возможностей ЯП еще и вызов зависимости выносится за скобки, т.к. есть возможность создавать AA>вычислительные выражения.
Это всё хорошо, но всё-таки на разных языках надо писать по разному, не надо сводить к общему знаменателю. А "могу писать фортран-программы на любом ЯП" — это, извините, банальное ламерство.
AA>"польза" Dependency Injection для ЯП типа C# очевидна. Не понимаю, почему нужно изобретать велосипед, когда есть отличная билт-ин реализация в коре(быть может вы все еще работает на легаси, тогда — да, только лисопед)?
Так DI или DI-контейнер?
Конкретно контейнер ничего полезного не делает, только сложность создаёт на пустом месте. В этом и проблема.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sharov, Вы писали:
VD>>Это как бы проблема не IoC/DI, а использования базовых типов (интерфейсов и базовых классов) в проектировании. А она вызвана тем самым D из SOLID, т.е. dependency inversion principle, а не DI/IoC. DI лишь механизм для упрощения dependency inversion principle. Ты путаешь причину со следствием. S>Это именно проблема IoC/DI, т.к. он может конфигурироваться из файла. При D. Inversion и без D. Injection у нас все равно как минимум S>одно использование типа в коде будет (кто-то же должен его создать?). А при создание может конфигурироваться хрен знает где S>и хрен знает как, и студия может об этом вообще ничего не знать. Вот и показывает, что тип нигде не используется, хотя S>он явно прописан в конфиге как зависимость.
Особенно помню была мода на использование какого-нибудь component-scan, да ещё и по регекспам.
Т.е. если класс попадает в classpath и его имя удовлетворяет каким-нибудь регекспам, он имплементит некие интерфейсы, имеет некие аннотации — это всё влияет на его судьбу. Вот уж веселуха такое отлаживать! Зато "кода меньше™".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, VladD2, Вы писали:
МП>>Они затрудняют распутывание кода, VD>Есть такое. Но это скорее проблема реализации DI.
Не, это принципиальная проблема, когда код инициализации никак статически не связан с кодом использования.
МП>>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) VD>А вот это точно полная чушь. Прекрасно рефакторю код напичканный DI с помощью Райдера.
Не так уж и прекрасно. Найти конкретную реализацию в конкретном месте использования не всегда просто, а иногда возможно только в рантайме.
S>Не до конца понимаю почему это плохо? Я сейчас уже IaC (infrastructure as a code) во всю, чем плоха S>конфигурация как код? Тестирование -- это все-таки организационный вопрос, а не принципиальный.
Потому что "организационную проблему" нельзя надежно решить раз и навсегда. Нельзя написать "организационный тест-кейс".
Код потому и переносят в конфигурацию, чтобы не тестировать.
IT>С чего бы? Я хочу настроить зависимости — я открываю один-два-три файла, а не с десяток и путешествую по ссылкам.
Это и есть IoC, Inversion of Control, вместо того, чтобы контролировать зависимости из source of truth (места, где зависимости возникают), приходится создавать новый "source of truth" (граф зависимостей, частенько на другом языке, скажем, XML вместо Java), и уже от него плясать.
Более удобным подходом было бы уметь генерировать граф зависимостей путем непосредственного анализа исходного кода. Однако там есть свои грабли, например, как понимать такую зависимость:
if (random(100) < 50) then add_dependency_on(AnotherComponent)
Утрировано, но смысл понятен — этакая "зависимость шредингера". Которая попросту не может быть надежно разрешена нигде, кроме конкретного экземпляра рантайма. И не надо говорить, что это редкий случай — это везде и всюду, просто чаще пишется так:
if (getenv("SOME_ENV_VARIABLE") == "true") { add_dependency(Something); }
И если такой код не содержит автотестов для всех вариантов этой самой SOME_ENV_VARIABLE, то все, попробуй угадай, что там будет происходить в реальной системе. Особенно если код addDependency сложный — например, скачивает Chromium и собирает его на локальной машине.
Здравствуйте, Sinclair, Вы писали:
S>А когда мы полагаемся на "уолшебный контейнер", то обнаружение кольцевой зависимости откладывается до рантайма. Поэтому такой код можно прекрасно закоммитить в репозиторий; и он даже может ездить в продакшне — до тех пор, пока кто-то не попытается активировать сервис доставки при помощи конфигурации.
Ещё добавлю. Дело не только в циклах, но и в элементарном разрешении зависимостей.
if(A) then add(DeliveryService)
if(B) then add(PigeonTransportService)
И отсюда ты фиг узнаешь, что на самом деле кто-то добавил в DeliveryService зависимость от PigeonTransportService и теперь от верности условий A и B у нас что-то будет падать, что-то не будет и очень хороший шанс, что обнаружится это только в проде. Тогда как при ручном описании — эта проблема станет заметна уже в IDE.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Министр Промышленности, Вы писали:
МП>>>С моей профессиональной точки зрения DI фреймворки не нужны.
AA>>Какой велосипед предлагаете взамен?
МП>так а зачем вообще делать dependency injection?
Уменьшить связанность компонентов.
Повысить гибкость поведения. клиент просит интерфейс, а в конфиге можно указать реализацию.
Идеальная система настолько модульна, что можно в рантайме выгружать и загружать модули
(такое есть в блэкбокс — можно выгрузить любую библиотеку если нет активных ссылок).
Здравствуйте, takTak, Вы писали: T>все разговоры в этой теме от банального непонимания, для чего придумали DI / IoC
Это верно.
А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers.
Вот тут очень хорошее объяснение для кого это нужно и как использовать.
Но это многе не понимают, и даже когда пишут микросервис из 5 классов, втыкают туда контейнер, "ибо надо". Но объяснить для чего — не могут. T>ну так вот тебе надо ПРОТЕСТИРОВАТЬ, что схема базы данных, используемая твоим кодом, актуальна, например,
А ещё многие не понимают разницу между выделением интерфейсов, IoC, DI, контейнерами. T>как ты с твоим этим кодом будешь писать код, который это проверяет?
var mainService = new MainService(new InMemoryProvider());
...
testThis(mainService);
testThat(mainService);
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
T>>система при DI делится на кучу заменямых компонентов, так что сделанные изменения проще тестировать, T>>если все прошлые изменения покрыты тестами (а когда есть DI, это сделать в разы проще), то прогонка этих же регрессионных тестов может указать на явные ошибки, когда ни тестов ни нет, то поддержка по становится чем-то вроде ходьбы по канату на высоте 100 метров без страховки T>>применимо это к так называемым оо — системам, при фунциональщине тесты базируются на иной парадигме, там композиция делается по-другому
TG>Понял. Спасибо. Вопрос: в описываемом случае с легаси ограничитесь извлечением интерфейсов или еще и DI-контейнер используете?
в этой ветке я уже в другом месте писал, что для новых проектов смысла в DI без IoC контейнера не вижу: слишком много приходится делать тогда своими ручками, сo старым же проектом трудно оценить, насколько безопасно и оправданно его переписать так, чтобы можно было бы покрыть критические части, которые будут изменяться, юнит или ещё какими тестами
вообще, из этого топика я понял, что есть .ева куча народа, которая пользуется DI лишь для композиции приложения из разных кусков, совершенно не задумываясь о таких вещах, как тестирование, имхо тогда смысла в DI просто нет: тогда , действительно, возникает сложно собранная гибкая система, гибкость которой ни в каких тестах не используется, а в других местах гибкость редко имеет смысл, ну тут приводили в качестве примера какие-то плагины: ну там это требование на уровне используемого фреймворка, ну такое...
если же ты начинаешь писать тесты, то тогда польза от использования IoC container сразу налицо: все архитектурные промахи (а для меня они заключаются в том, что невозможно или крайне трудно написать изолированные юнит-тесты ) сразу же явно проступают, но какое-то время на изучение того, что такое IoC container и с чем его едят, уходит, я бы тут порекомендовал почитать какую-нибудь книжку под заданный язык o DI и книжку про тестирование, чтобы было понятно, к чему надо стремиться
Здравствуйте, Министр Промышленности, Вы писали:
МП>>>вопрос был в разрезе целесообразности использования DI МП>>>если можно использовать нормальные конструкторы, зачем там применять DI? МП>>>или даже зачем заменять нормальное конструирование дополнительными DI сборками и конфигурированием этого всего
M>>Как вообще можно писать бизнес приложения без DI ума не приложу
МП>подозрительное заявление
Не знаю на чем вы пишите, но на C# DI уже давно входит в базовый фреймворк и без него давно никто не работает
Не обязательно весь проект, просто пример как вы пишите классы решающие бизнес задачи, как передаете в них зависимости?
В каком месте приложения конструируете зависимости?
Зачем делаете это руками если есть контейнер который справится с этим сам и без проблем?
Примененные где не надо — конечно, зло.
"Фабрики" это лишь способ добавить уровень абстракции к оператору создания объекта.
Придумывая красочное сравнение, представьте, что вы ко всем 220В розеткам заранее приделаете "универсальный переходник", который будет позволять подключать какие попало вилки. С одной стороны, удобно, можно не заботиться о том, что за электроприбор вы подключаете.
Но в реальности эти переходники оказываются не нужны почти никогда. Зато они добавляют лишних контактов и соединений, делают розетку менее надежной, а потом и вовсе приводят к пожару, потому что не были рассчитаны на ток, который там случайно оказался.
M>>>покажите пример, а мы посмотрим. МП>>это не вполне легально по нынешним временам МП>>но всё что я сам писал было без DI кроме мест в проектах где это было уже мне навязано через легаси
M>Не обязательно весь проект, просто пример как вы пишите классы решающие бизнес задачи, как передаете в них зависимости?
это всё решается грамотной иерархией сборок по слоям
есть уровень простейших объектов, data-object
сборки этого уровня никуда не ссылаются
есть уровень helper'ов, DAL и технической логики
сборки этого уровня могут ссылаться на сборки предыдущего уровня
и есть уровень содержательной бизнес логики, который уже может ссылаться на все предыдущие уровни, но по необходимому минимуму
циклических зависимостей тут в идеале вообще возникнуть не должно
но если возникают, вы же знаете как их решать?
(создание интерфейса нужной функциональности, вынос его в нижестоящий слой, а реализация может быть и в вышестоящем слое)
M>В каком месте приложения конструируете зависимости?
я их не конструирую, а избегаю
но речь я так понял о конструирования высокоуровневых объектов?
ну так там и конструирую где они нужны
либо если это кеш — при инициализации или обновлении кэша
var a = new A(12, "6")
{
Field1 = new B(),
Field2 = C.Instance;
}
M>Зачем делаете это руками если есть контейнер который справится с этим сам и без проблем?
с чем это он справится сам?
он явные аргументы конструирования перенесёт в отдельное место (совершенно неявное)
чем создаст шов в восприятии
а то и вообще в конфиг файл
но суть работы по инициализации всё равно нужно будет делать
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>Они затрудняют распутывание кода,
Абсолютно не затрудняют на практике. Нет, возможно и можно что то там наворотить при желании, но это нужно специально стараться.
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
Не понижают, прекрасно все рефакторится.
У них основной недостаток — это завязанность на DI фреймворк или библиотеки, соответственно если ты хочешь часть кода на одном проекте, использовать в другом проекте, в котором другой DI фреймворк или такого нет вовсе — будут некоторые проблемы, придется пошаманить. Плюс недостаток — это некоторое незначительное увеличение размеров кода, без DI будет короче, если забить на тесты.
На деле, без DI да, жить можно. Но лишь при поддержке языка. И для чего то небольшошо, например библиотек или мелких проектов. В случае, когда проект становится достаточно сложный (а это практически весь энтерпрайз), бизнес логика раздувается, ты уже начинаешь сам думать о DI, и либо юзаешь готовый фреймворк, либо тебе придется писать свой лисапед, благо это несложно.
Собственно я живу без DI уже более 5 лет. Но у меня не энтерпрайз, могу позволить, плюс не исключено что придется переписывать на язык типа Rust некоторые части, плюс у меня язык специфический, плюс части кода потом уходят в библиотеки и используются в других проектах. При этом иногда жалел, что не стал тяжелую артиллерию подключать — через определенное количество времени уже понадобились навороченные фичи, и пришлось в результате некоторые лисапеды городить.
Здравствуйте, Sinclair, Вы писали:
S>Ну это же прекрасный пример. Через три-четыре релиза у нас в топ жалоб выезжает "ваше приложение нещадно тормозит", привлекается команда высокооплачиваемых экспертов, которые после трёх месяцев расчистки конюшен констатирует очевидное: "у вас там каждая кнопка в гуе считает, что ей нужно лазить в базу/сторонний сервис/ещё куда-то, в итоге для построения одного экрана открывается 27 коннектов к базе, и исполняется более трёхсот SQL-запросов. Из них треть дублируют друг друга, а половина, хоть и не совпадает, может быть покрыта одним из более широких запросов". Рекомендация экспертов: "чётко разделить фазы построения гуя, доставая данные из базы минимальным количеством запросов и передавая визуальным компонентам готовую viewModel".
Добавлю ещё дополнительно: само собой я не в курсе необходимости кэширования данных, не знаю о том что нужно минимизировать количество коннектов, не в курсе что оптимизировать приложение (вопреки Кнуту) нужно с самого начала, а до простого примера нужно обязательно докопаться.
S>в итоге для построения одного экрана открывается 27 коннектов к базе, и исполняется более трёхсот SQL-запросов.
Про настройку времени жизни в DI и request-scope я так понимаю вы ни сном ни духом?
Здравствуйте, Sharov, Вы писали:
S>А DI то тут причем? Не вижу связи с оптимизацией sql запросов, что правильно, и DI. Тоже самое отменно может быть S>и без плюшек DI.
Ну, во-первых, давайте всё же не путать "DI" и "DI Container".
Сама по себе DI ничего плохого не содержит.
А вот "плюшки DI" — это уже, как правило, и есть DI Container. И аргумент тут как раз был именно такой — "вот я просто взял, добавил аргумент IDBConnection в свой конструктор, и мне не надо думать, откуда он возьмётся — контейнер его мне притащит, как собака тапки".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, varenikAA, Вы писали:
AA>Здравствуйте, barn_czn, Вы писали:
_>>Все равно ни понял. Контексты всегда были и без DI (Control.Parent, Control.Application, Request.Host и т.д). Добирайся нихочу. _>>Но вот если есть конструктор у App, а я явно не вижу место его вызова — меня это напрягает. Таких неявных мест должно быть по минимуму.
AA>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов.
я таких учебников не читал.
AA>Взять теже фабрики, билдеры они тоже зло?
Что есть фабрика? Если это метод который внутри вызывает new MyClass, потом этот метод передается как делегат — отлично, потому что эта связь явная и ее легко отследить.
А если это в каком то магическом месте неявно задано, как с DI, — я не понимаю.
Дальше, говорят что плюс в том что из любого места я могу получить(создать) любой инстанс. Но простите, в таком случае ваша архитектура превратилась в кучу глобальных переменных где Всё доступно Отовсюду. Как же тогда быть с принципами разграничений? В определенном месте кода должно доступно только то что необходимо, что передали явно, что доступно по ссылочным связям.
Здравствуйте, microuser, Вы писали:
M>Так не пишут при использовании DI. Нужно постараться уйти от зависимости которую нельзя разрешить автоматически. Как вариант SourceType можно попробовать перенести из конструктора в DoSomeWork.
Т.е. какой-то там драный фреймворчик будет указывать мне, программисту с тысячелетним опытом как писать код???
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, varenikAA, Вы писали: AA>Да и вообще, оператор new не нужен в принципе. В куче ЯП его нет вообще. К чему эта магия? AA>Дельфи — :=Create("Alice"), лисп — (make-person :name "Alice"), f# let person = Person {Name = "Alice"}.
Вопрос не в операторе. В том же Delphi паттерн "фабрика" встроен прямо в язык — см. "виртуальный конструктор".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
Сложно ли ездить на машине?
Если не умееешь — да.
Если Да — ходите пешком, или даже изобретите велосипед(эх, прокачу!).
Нормальные люди выберут феррари.
Если придется выбирать между самодельным велосипедом
и промышленной разработкой в которую вложен труд десятков специалистов,
и которая проверена многолетней практикой.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, Somescout, Вы писали:
IT>>>При желании можно абстрагироваться даже от интерфейсов. Но я не буду говорить как, не дай боже вам это понравится. S>>Само собой не будете: вы уже который комментарий стесняетесь не то что код приводить, но даже хоть чуть-чуть в конкретику углубляться.
IT>Вроде как код собираешься приводить ты, а не я.
Вы б документацию к своему LinqToDB с таким же энтузиазмом пилили, как балаболили на форуме.
Здравствуйте, Sharov, Вы писали:
S>Не до конца понимаю почему это плохо? Я сейчас уже IaC (infrastructure as a code) во всю, чем плоха S>конфигурация как код?
Тем что тут с точностью до наоборот, код как конфигурация.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Непонятно. SL подразумевает, что оттуда что-то можно получить, т.е. Resolve тебе найдёт что надо и выдаст. В то время DI же ничего не получает, ему всё дают. НС>Ты зарплату не получаешь, ее тебе дают?
Вот это софистика.
НС>К чему эта софистика?
Это ключевое слово: Inversion of Control.
НС>·> Как используя DI можно что-то получить? НС>Объявить параметр в конструкторе.
Параметр объявляет тип, а инжектится инстанс. И соотвественно в случае SL надо вызывать Resolve для того чтобы получить инстанс для данного типа.
В динамических языках, где все типы "object" — такой вопрос о типах стоять не будет, а SL/DI таки будут различаться.
НС>·> Это противоположные вещи. НС>Нет.
Это твоё личное понимание. Общепринятое понятие — я тебе уже приводил цитату
DI directly contrasts with the service locator pattern, which allows clients to know about the system they use to find dependencies
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, maxkar, Вы писали:
M>Ну... Очень просто.
M>*] Помечаем C как "under construction" M>*] Смотрим, как его вообще можно создать. M>*] Видим, что нужно B M>*] Видим, что B у нас еще нет M>*] Рекурсивно выполняем этот же алгоритм M>*] Создаем C M>*] Успешно заносим C в реестр
Поздравляю тебя. Ты сейчас описал метод построения графа зависимостей. Замени объекты на вершины, а "under construction" на флаги пометки обхода и вуаля.
Собственно проблема в том, что в таком подходе проблемы вявляются в рантайме, что плохо. Чем раньше известно о проблеме, тем проще и дешевле ее устранить.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Буравчик, Вы писали:
Б>·>А для теста Б>·>
Б>·>new AppRoot(new MockDb(), new MockFilesystem(), new MockClock()...).run();
Б>·>
Б>Ну что, если мне не все зависимости надо мокать, а только две из десяти? Б>Остальные восемь мне хотелось бы взять "настоящие", уже подготовленные
Я не очень понял требования. Но в целом подход один и тот же. Ты описываешь явно что и как зависит в разных вариантах использования, где это надо использовать и для чего.
Допустим у тебя есть 10 чисто системных зависимостей. Т.е. у тебя AppRoot с 10 зависимостями и он используется в двух местах — в прод коде и в тесте. Притом в этих двух местах ты подсовываешь все разные зависимости.
Если вдруг тебе понадобится некий более крупный интеграционный тест, у которого только 2 зависимости из 10 отличаются от прода, то рефакторингом извлекаешь метод что-то вроде: createAppRoot(SomeDep3 d3, SomeDep10 d10) {return new AppRoot(new SomeDep1(), ...d3, new SomeDep9(), d10);} и используешь его в проде и в этом тесте, тогда как тот старый тест использует AppRoot напрямую, заменяя все 10 зависимостей.
Если где-то внутрях понадобится ещё некая 11-я зависимость, которую ты захочешь подменять в тестах, используешь рефакторинг introduce parameter и поднимаешь зависимость наверх до того места (AppRoot или createAppRoot) где нужно подменять.
И наоборот, если зависимости стали общими, то втаскиваешь их обратно.
В совсем сложных случаях сами зависимости могут собираться во множество модулей, которые сами по себе становятся компонентами с зависимостями и их можно объединять в ещё более крупные структуры. Т.е. получается иерархическая архитектура структуры приложения.
Т.е. для структурирования Composition Root используются те же техники декомпозиции, которые ты используешь для любого другого кода.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, IT, Вы писали:
IT>У меня нет множества сервисов, только база данных. Как тут поможет DI фреймворк?
у меня, например, был проект, в котором "только БД" клиент был вынужден шардировать на 4 отдельных инстанса. DI спас, так зависимость была строго по модели и интерфейсу её прокачки в обе стороны, а не источнику(ам).
Б>> "Pure DI" vs "DI Container".
S>А в чем отличие?
в одном случае ты всё делаешь сам на своих коленках и с использованием своего велосипеда, который никому, кроме тебя, неинтересен,
в другом случае (опять же, никаких "DI container" нет, есть "IoC container" ) ты пользуешься трудом тех, кто до тебя на сотню-другую граблей уже наступил
Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
Я их тоже недолюбливаю, но понимаю их выгоды (как и недостатки).
Есть случаи когда DI вообще удобное решение. Например, когда сам продукт — это расширяемая среда вроде MS VS. Представить скажем подсветку кода в вид инжектящегося компонента удобно и красиво. Не надо писать левый код регистрации. Все выглядит декларативно и красиво.
То что DI решает проблему написания кучи кода создающего объекты и передающего их в другие конструкторы тоже верно. Иногда объектов может быть очень много. Но есть тут и минусы. Код завязанный на коркретный DI хрен перенесешь в другой проект. Иногда DI заставляет вводить лишние абстракции. Скажем вводить интерфейсы там где у них никогда не будет более одной реализации. Иногда DI приводит к тому, что хрен поймешь, что за тип к тебе в реальности приехал и где та магия, которая к этому привела.
Многие проблемы DI происходят от того, что DI-фрэймворки динамические, создают граф зависимостей в рантайме. Если бы зависимости разрешались бы статически, при компиляции море проблем DI ушло бы.
Ну, а по жизни все просто. Вот есть в продукте DI и все через него сделано и ты вынужден использовать его и писать в соответствующем стиле. А если тебя свой проект, то выбор — это архитектурное решение.
Лично я обычно с неохотой иду на использование DI. Не поверите, но код отлично можно писать в процедурном стиле.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Министр Промышленности, Вы писали:
МП>я райдером не пользовался до сих пор МП>у него есть фичи сверх решарпера?
С памятью проблем нет, так как 64битный. А так почти тоже самое.
МП>использование элементов класса может быть неявным через IoC-фреймворк (сейчас понимаю лучше формулировать так) МП>увидев в IDE 0 количество использований — придётся озираться, а не использует ли его IoC-фреймворк, а не удалить сходу
Это как бы проблема не IoC/DI, а использования базовых типов (интерфейсов и базовых классов) в проектировании. А она вызвана тем самым D из SOLID, т.е. dependency inversion principle, а не DI/IoC. DI лишь механизм для упрощения dependency inversion principle. Ты путаешь причину со следствием.
Ну, и если правильно пользоваться Райдером (думаю и Решарпером тоже), никаких проблем найти все реализации нет. Постоянно этим пользуюсь. F12 перейти к реализации. Ctrl+F12 — получить список реализаций или перейти к единсвенной.
В рантайме опять же можно видеть реальный тип.
Если использовать статический DI, во многих случаях можно будет конкретный тип предусмотреть.
Ну, а соблюдение Liskov substitution principle, т.е. L из SOLID позволяет не париться с тем, что у нас там за интерфейсом скрывается. Хотя, конечно, это больше самообман. Но если это не делать проблемы будут и без DI.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
S>Выше уже обратили внимание, что D. Injection D. Inversion не одно и тоже. В SOLID речь идет об Inversion, S>для этого Injection можно не использовать, а просто придерживаться соотв. принципов разработки и проектирования.
это один из моих любимых вопросов на интервью. потому как, что такое DI, понимают многие, а вот DIP, увы, единицы.
S>Достигается легко -- везде и всюду использовать интерфейсы или базовые классы.
не всегда так.
S>Контейнер же вообще не требует от программиста написания оператора new в явном виде, кроме всяческих S>абстрактных фабрик и не более.
он и он никак не решает вопросы с DIP. а так же с L.
Здравствуйте, ·, Вы писали:
Б>> Чуть менее удобно переопределять зависимости для тестирования — приходится создавать наследника для переопределения методов. ·>Это ерунда какая-то, к DI прямого отношения не имеет.
Задача в том, чтобы сделать интеграционный тест для некоторого сервиса.
1. Мы может собрать зависимости вручную, и собирая, сделать замену зависимостей на стабы/моки
2. Можем вытащить нужный сервис из composition root, но предварительно заменив некоторые зависимости в контейнере.
Я так понимаю, что ты предлагаешь первый вариант. Но второй вариант на мой взгляд лучше:
1. Это гарантирует, что сервис будет собран именно так, как в продакшене (кроме подмененных зависимостей)
2. Тесты будут требовать меньше изменений при изменении зависимостей.
Чтобы реализовать переопределение зависимостей без использования контейнера создаем TestAppRoot (наследуется AppRoot), в котором переопределяем несколько create-методов.
AA>по мне так это философия. как только нужно расширить поведение объекта, так сразу возникает необходимость интерфейса и следовательно фабрики.
Не вижу связи. Фабрика — это лишь способ добавить turing-full код туда, где изначально его не было. А DI — это способ вернуть с уровня turing full на DSL (см. configuration complexity clock). И то, и другое — в runtime, то есть поздно и не особо безопасно.
Здравствуйте, VladD2, Вы писали:
VD>Лично я обычно с неохотой иду на использование DI. Не поверите, но код отлично можно писать в процедурном стиле.
Рискую быть непонятым, но скажу, что DI это даже круче — в некотором роде это ФП-стиль построения большого приложения. Как в ФП мы передаем замыкание ("настроенную" функцию), так в DI мы передаем зависимость (уже "настроенный" сервис).
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Ночной Смотрящий, Вы писали:
НС>>Тебе тогда тот же вопрос. Какая обязательная фича DI контейнеров требует " расчет графа зависимостей"?
VD>А ты попробуй представить упрощенный алгоритм создания экземпляра класса. Вот есть у тебя классы A, B и C. Они зависят один от другого. Скажем C от B и B от A. Как создать C?
Ну... Очень просто.
Помечаем C как "under construction"
Смотрим, как его вообще можно создать.
Видим, что нужно B
Видим, что B у нас еще нет
Рекурсивно выполняем этот же алгоритм
Создаем C
Успешно заносим C в реестр
Если вдруг в процессе создания наткнулись на "under construction", есть два варианта. Если зависимость идет через конструктор — пишем ошибку. Если зависимость идет через поле — записываем сеттер, который нужно выполнить после инициализации зависимости.
Никаким "расчетом графа зависимостей" здесь даже не пахнет. Есть что-то неявное, но до графа ему далеко. Причем так делает аж целый Spring — de-facto standard container в мире java!
Почему знаю... В прошлом году аккуратно копался палочкой в легаси. Ну и что-то там менял. Заодно менял property injeciton на constructor injection, чтобы уменьшить количество циклических зависимостей. И вот в какой-то момент запуск (injection) стало падать. Сначала — под windows (на linux и mac — нормально). Потом и на некоторых маках. И ругалось оно на "циклическую зависимость" (бонусом, Spring вываливает весь стек и даже не пытается показать, где там начинается цикл). Что я могу сказать? Ну была там циклическая зависимость, да. Но если построить граф, она прекрасно разрешалась. По конструкторам был ясен порядок создания, остальное (properties) можно было доинициализировать. А вот если пытаться наивно создать с произвольного объекта — все с некоторой вероятностью падает.
Так что не стоит надеяться на то, что в контейнере грамотно реализованы подходы. Потому что графы — это не модно, а вот модификация байткода в процессе загрузки (которую Spring тоже делает) — самый шик.
Вероятно, есть более правильно реализованные контейнеры. Но это же нужно тратить время на изучение контейнера. На изучение его граблей. На обход его граблей. Затем на обход сложностей реализации декораторов (они очень плохо контейнерами инжектятся). Потом на обход еще чего-нибудь...
Здравствуйте, СвободуАнжелеДевис, Вы писали:
САД>он и он никак не решает вопросы с DIP.
Он упрощает\помогает в решение этого вопроса.
САД>а так же с L.
Про L не скажу, но думается зависит от конфигурации и прочих продвинутых плюшек.
Т.е. где надо вернуть такой-то тип, где-то такой-то. По идее это как-то настраиваемо
в том же Castle.Windsdor.
M>Никаким "расчетом графа зависимостей" здесь даже не пахнет.
Пахнет, конечно — это как раз и есть самый примитивный (не значит что плохой!) механизм расчета. Занятно, что именно так оно много где реализовано, скажем, в том же Erlang'e, весь алгоритм — 10 строчек.
Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
МП>>так а зачем вообще делать dependency injection?
AA>Уменьшить связанность компонентов.
принимается
AA>Повысить гибкость поведения. клиент просит интерфейс, а в конфиге можно указать реализацию.
не надо так делать!
если очень надо, то можно
но это же несёт уже упомянутые мной издержки
уже со времён WCF сервисов заметил, что сложность конфигурирования зачастую превышает сложность кодирования
AA>Идеальная система настолько модульна, что можно в рантайме выгружать и загружать модули
это красиво, но не безопасно
угроза со стороны расщепления внимания
выгрузка и развёртывание — и так напряженный момент
в идеале она должна сводиться к аккуратному копированию файлов
или даже лучше — к удалению каталога и просто копированием нового целиком
а вы предлагаете в рантайме мутить сборками
AA>(такое есть в блэкбокс — можно выгрузить любую библиотеку если нет активных ссылок).
если это делается автоматически, то это плюс
если об этом надо думать — это минус
AA>>>Какой велосипед предлагаете взамен? AA>ПС. не ответили на мой вопрос.
я не предлагаю велосипедов, вполне можно использовать популярные DI фреймворки там где это реально необходимо
а реальная необходимость бывает редко и всего в 2-3 местах системы
я видел что часто суют прочто чтобы было
это конечно боль программных проектов — разработчики тренирубтся за счет работодателя реально в ущерб энтропии проекта
но когда ещё начинают требовать — это на мой взгляд невменяемость сознания
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
vsb>>>>Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
МП>>>нормальные обычные конструкторы, GIV>>не понял какие конструкторы у тебя нормальные но DI не накладывает никаких ограничений на это. МП>>>ну либо статические конструкторы, GIV>>их DI отменяет или что?
МП>вопрос был в разрезе целесообразности использования DI
МП>если можно использовать нормальные конструкторы, зачем там применять DI? МП>или даже зачем заменять нормальное конструирование дополнительными DI сборками и конфигурированием этого всего
Нормальное DI как раз и работает через конструкторы. Как вообще можно писать бизнес приложения без DI ума не приложу, покажите пример, а мы посмотрим.
Здравствуйте, Министр Промышленности, Вы писали:
vsb>>Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
МП>нормальные обычные конструкторы, ну либо статические конструкторы, если нужен пул объектов...
Я не понимаю тебя.
У меня есть один объект. Например назовём его UserDao. И есть второй объект. Назовём его AuthService. Для AuthService нужна ссылка на UserDao. Каким образом он эту ссылку получит?
Сконструировать он его не может. Во-первых я просто не хочу, чтобы в системе было много UserDao, например там может быть кеширование или ещё что угодно. Во-вторых для создания UserDao мне нужно соединение к БД, возможно ещё что-то. Передавать всю эту кучу скопом в AuthService (и в любой другой объект, которому нужен UserDao) это маразм.
Если я правильно понял про "статические конструкторы", ты предлагаешь сделать что-то вроде UserDao.getInstance(). То бишь реестр объектов, причём плохой и неудобный реестр. Так делать можно, но при этом возникает уйма проблем:
1. Такой код сложно тестировать. Если я хочу, чтобы AuthService принимал фейковый UserDao, мне нужно вызывать в тесте UserDao.setInstance(testUserDao). При этом мне в принципе нужно помнить при тестировании AuthService про все его зависимости. А ещё надо не забыть по окончании работы теста убрать все эти тестовые переменные, иначе это может поломать другие тесты. Ещё возникают вопросы с многопоточностью. В общем проблем много. Решать как-то их, наверное, можно, но неудобно.
2. Такой код сложно переиспользовать. Если мне всё же понадобится в реальном коде другой UserDao конкретно для AuthService, то это будет просто невозможно сделать в рамках данного подхода.
3. Непонятно, собственно, к чему мы пришли в итоге. У нас есть вызов UserDao.getInstance(), который возвращает что-то. Что он возвращает? Неизвестно. Кто-то там ему сделал setInstance раньше, его и возвращает. Какая разница с DI в плане понимания работы кода? Никакой разницы.
4. Сложно понять, от чего зависит AuthService. Нужно внимательно читать его код, изучать список импортов. В случае с DI ты просто смотришь на параметры конструктора и всё как на ладони. Это бывает удобно.
Здравствуйте, AlexRK, Вы писали:
ARK>Это меня на текущем проекте просто достало. Чуваки с золотыми молотками суют это уродство во все щели — и куда надо, и куда не надо (на самом деле почти никуда не надо). Гора интерфейсов, за которыми найти не возможно просто ничего. Тьфу.
Я такое при возможности выкорчёвываю нещадно. На самом деле есть очень простой и эффективный критерий правильно написанной бизнес логики. Если логически законченный функционал бизнес логики легко извлекается из одного проекта и спокойно переносится в другой, то это правильно написанная бизнес логика. Если же для переезда в соседний дом нужно перетащить с собой весь подъезд, то это не код, а кусок оверинжениринга.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, vopl, Вы писали:
G>>Уже? Доброе утро. Погугли что означает D в SOLID V>Попробуй сам погуглить, что же именно означает D в SOLID
ололо
inversion это не injection
Здравствуйте, varenikAA, Вы писали:
AA>Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
Выстрелив в ногу ее надо потом отрубить, чтобы не мешала...
AK>При чём здесь строковые константы? Я не понимаю. AK>Можете дать какой-нибудь пример?
Я похоже переврал, что конкретно можно делать только resolve по имени, что ослабляло бы типизацию.
Но вот дискуссия на SO, которую я считаю релевантной -- https://stackoverflow.com/a/4988337/241446
В частности вот это:
Errors are pushed to run-time. If you configure your Guice module incorrectly (circular reference, bad binding, ...) most of the errors are not uncovered during compile-time. Instead, the errors are exposed when the program is actually run.
Т.е. из-за DI некоторые ошибки будут заметны на стадии исполнения, нежели при компиляции. Вероятно это ТС имел в виду.
Здравствуйте, Министр Промышленности, Вы писали:
МП>
МП> var userDao = DatabaseHelper.GetUserDao();
МП> var authService = new AuthService(userDao);
МП>
Это, вобще-то, и есть dependency injection. Только вы это вручную делаете, и фактически переносите проблемы на уровень выше: теперь вызывающий код в обязательном порядке должен иметь ссылку на базу (напрямую или через DatabaseHelper), и сам должен получить userDao — что поменялось в итоге от такой перетасовки? Ничего — все проблемы ровно на том же месте.
Здравствуйте, Poopy Joe, Вы писали:
S>>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI? PJ>Отличное описание. DI это костыль, когда лень думать об архитектуре, но не лень потом все это разгребать и отлаживать.
Да считайте как угодно. Я успел прочитать несколько ваших комментариев — не вижу смысла спорить с троллем.
Здравствуйте, Somescout, Вы писали:
TG>>А почему оставим-то? Это очень интересный вопрос. S>Потому что пример абстрактный.
Обсуждать абстрактные примеры не интересно.
Если люди видят пользу от DI, то в чем проблема привести конкретный пример, где он давал бы видимые преимущества и привносил не очень много проблем.
TG>>Или получается, что DI полезен только вот в таких "хитрых" архитектурах? S>Что "хитрого" в такой архитектуре?
Фраза "Допустим мне в глубинах компонентов понадобился доступ к базе" намекает, что при проектировании несколько подзабыли принципы SOLID, разделение на слои и т.д. И если сделано это осознанно, то было бы опять же интересно посмотреть на конкретный кейс.
МП>ок, хотя можно было это решить и без DI фреймворка в проекте
да всё что угодно можно сделать без DI. вопрос в проекте.
его размере, тестировании.
у нас есть солюшен на 30+ проджектов. авто-регистрация зависимостей очень облегчает жизнь
Здравствуйте, barn_czn, Вы писали:
_>Конкретный пример пож-та, чтобы спор был не абстрактным.
И да, еще больше примеров в книжке "Роберт Мартин — Чистая архитектура — 2018", после ее прочтения сомнения отпадают.
Так или иначе зависимости будут, использовать для этого ФВ или делать свой велосипед это уже от архитектора зависит.
Хотя да, большинство проектов на C# это монолиты.
В корэ использую стандартный DI очень доволен. можно в любом месте приложения получить ссылку на любой объект не задумываясь.
Здравствуйте, Poopy Joe, Вы писали:
PJ>Здравствуйте, microuser, Вы писали:
M>>Интересно было бы посмотреть на вашу архитектуру которая делает DI не нужным. PJ>По мне, так DI не просто не нужен, а только вредит. У кода выше из-за DI сразу несколько проблем, причем на ровном месте: требуется состояние, неявная параметризация функций, смешивание бизнес-логики и сторонних эффектов, неявная зависимость от внешнего поведения. PJ>Серебряной пули тут нет и каждые случай "необходимости" DI решается по разному, но при этом оставляя бизнес-функции чистыми. В общем случае все сводится к тому или иному виду композиции, а функция в параметрах получает то, что в случаи DI запрашивает сама из внешних источников. В языках где есть частичное применение параметров для этого вообще ничего специального делать не надо, но в том же c# можно возвращать Func вместо результата. Для логгера можно использовать монаду, пример.
Не совсем понимаю связь между неявной параметризацией функций, смешиванием бизнес логики и зависимостей от внешнего поведения.
Все это вопросы архитектуры, а DI фреймворк лишь берет на себя ответственность за создание экземпляров объектов, работать он может в любой архитектуре.
Хотелось бы real life пример бизнес приложения, или хотя бы CRUD hello world без DI.
Здравствуйте, Somescout, Вы писали:
S>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI? Ок, я могу создать её просто конструктором, но нужна строка подключения. Которая задана в конфигурации. Которая взята из файла/реестра/сервера. Следовательно чтобы просто создать контекст, мне нужно сначала прочитать конфигурацию, получить из неё строку подключения, а затем уже подключиться к базе. Выглядит геморройно (а уж сколько веселья при рефракторинге, если потребуется поменять способ построения контекста).
S>А можно вместо этого просто прописать в конструкторе требование к наличию этого объекта — и всё, DI автоматически создаст его без каких-либо телодвижений.
Какая прелесть! А кто мешает один раз написать для базы данных какой-нибудь рапер/хелпер, который будет решать все эти вопросы без DI?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, vsb, Вы писали:
vsb> Поздравляю, вы только что показали пример использования паттерна Dependency Injection. vsb> А фреймворк даст возможность не писать эти две строчки.
Это лукавство. Вместо этих двух строчек надо будет строчки в конфигурации и пара аннотаций, которые хрен протестируешь.
vsb> Которые разрастутся в сотни и тысячи строк однотипного скучного кода в реальном приложении с десятками-сотнями объектов
Точно. А вот конфигуации прямо таки веселье и праздник!
Здравствуйте, Somescout, Вы писали:
IT>>Какая прелесть! А кто мешает один раз написать для базы данных какой-нибудь рапер/хелпер, который будет решать все эти вопросы без DI? S>Ок, напишу я его, а прокинуть его как? И я опять же не понимаю: зачем "решать эти вопросы без DI" — как это облегчает работу?
Ты не правильно ставишь вопрос. Т.к. это ты добавляешь в проект новый инструмент, то вопрос должен ставится как "какую проблему этот инструмент решает, какие негативные последсвия вносит и каково их соотношение". Т.е. польза, вред, что больше.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
S>>А что мешает перенести инициализацию DI и всё тогда тоже будет работать? Не говоря уж о том, что если в прицнипе стоит вопрос о "использовать DI или нет", значит код уже нетривиальный и просто так не переедет.
IT>Давай мы сразу определимся. Речь идёт не о DI как таковом, а о DI фреймворках и всяческих IoC контейнерах. Ты сейчас про что?
О них в том числе. Я просто действительно не понимаю что именно вас в них смущает: у меня два проекта, когда из первого, построенного на DI потребовалось перенести компоненты во второй (без DI) — не возникло никаких проблем, я просто руками конструировал все объекты. Да, это неудобнее, да и управление временем жизни становится геморройным, но каких-то сложностей с переносом нет — ведь фактически это обычные классы без магии (DI выполнялся исключительно через конструкторы).
Здравствуйте, IT, Вы писали:
S>>Если у вас зависимость от множества сервисов, то прежде чем воспользоваться GetCustomer вам нужно их все получить. Да, можно обернуть это всё в Helper — но какой смысл плодить сущности без нужды?
IT>У меня нет множества сервисов, только база данных. Как тут поможет DI фреймворк?
Да я вроде уже в первом же комментарии это объяснял — вам не нужно конструировать контекст базы непосредственно внизу. А если абстрагироваться через интерфейсы, то компонент может вообще не знать с чем именно он работает — есть интерфейс для получения данных, он его и использует. А что за ним прячется: сама база, кэш или сервис — его не касается.
Здравствуйте, Министр Промышленности, Вы писали:
S>>>А вообще, в целом, я согласен с теми из участников дискуссии, которые считают развитые DI-контейнеры всего лишь средством переноса сложности из исходного кода в конфигурацию. S>>У меня складывается впечатление что некоторые участники дискуссии слегка побаиваются DI.
МП>потому что, мать его, столкнулись с последствиями его внедрения
То есть детский испуг? Ну накнулись на странную и непонятную штуку и "ААААА!!!!" (и под одеяло).
Здравствуйте, Somescout, Вы писали: S>Да потому же — вы говорите про 27 коннектов и 300 запросов, хотя как раз DI позволяет в такой ситуации переиспользовать один контекст с одним соединением и кэшированием запросов. Ухты. Ещё и кэширование запросов? Расскажите, что вы имеете в виду, а то я уже представил себе всяких ужасов.
Вот вы, скажем, Безумный Макс: Дорога Ярости смотрели? То, что вы описываете, выглядит наоборот: дорога крайне удобная, а вот в конце — всё как в фильме: муть, взрывы, кишки, и прочее.
сделать "а шандарахну-ка я тут запросец в базу". S>Ок, а если в этом месте нужны данные из базы — что именно вы предлагаете с этим делать?
Предлагаю добавить нужные данные во viewModel, и соответствующим образом подкорректировать код порождения viewModel, который уже ходит в базу. Потому что каждый roundTrip между апп-сервером и СУБД — это увеличение времени удержания соединения (если мы сумели привинтить повторное использование соединения). Внезапно окажется, что
1. В 70% случаев нужные нам данные уже есть во viewModel, просто кто-то поленился повнимательнее в неё посмотреть
2. В 20% случаев можно просто добавить ещё одно поле в уже существующий select, не изменив время исполнения запроса
3. В 10% случаев можно добавить ещё один селект в тот батч, который строит viewModel, минимально изменив время исполнения запроса.
4. В самом редком случае нам прямо потребуется специальный отдельный запрос, который мы пошлём уже после того, как получили ответы на предыдущие запросы. S>Нет, вы докопались до DbContext(знал бы что вас на нём заклинит — написал бы DAO или "Сервис получения данных (с) (tm)")
Было бы ещё хуже. Признайтесь честно — вы профилировали когда-нибудь SQL трафик ентерпрайз-приложения? Тридцатилетние архитекторы выходят из этой комнаты седыми.
И чем больше у нас там DAO или сервисов получения данных — тем ужаснее выходит результат. S>То есть с компонентными системами нахлебались, а с процедурными, которые тянут напрямую связи во все зависимые компоненты — нет.
До компонентных систем я нахлебался с "неявными связями" — это когда у нас есть набор переменных различной степени глобальности, и обращения к ним рассеяны по коду без малейшей разметки.
Ну, я и сам так писал — на клиппере. Меня извиняет только то, что мне было 11 лет. То же самое я наблюдал в ентерпрайз решениях на классическом ASP — это когда в Global.asa есть dbconn, и connectionStr. И ещё dbconn2 в global.inc, незнамо для чего.
И каждая страничка либо берёт dbconn, либо dbconn2, либо открывает своё подключение через connectionStr.
Понять, почему так — хз. Скорее всего, тупо из-за некомпетентности разработчиков. Исправить это — упаси меня байт: там пара тысяч таких страничек, разобраться, кто от кого зависит — нафиг-нафиг.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, petroa, Вы писали:
P>>Это ведь вроде, так или иначе, придётся делать самому. Можно делегировать ioc-контейнеру. Интересно ваше мнение.
IT>Дело не в конфигурации. Обрати внимание, нелюбители DI фреймворков сетуют прежде всего на то, что с таким кодом сложно разбираться. Это действительно так. Это сложно понять, если ты написал пару проектов исключительно как писатель и не занимался их поддержкой продолжительное время. А ещё лучше это прочувствовать на чужом коде. Вот представь, у тебя есть простейший код:
IT>
IT>class Foo
IT>{
IT> public Foo(Bar b)
IT> {
IT> if (b == null)
IT> throw ...
IT> }
IT>}
IT>
IT>Код кидает исключение. Твоя задача — найти причину. Если код без DI фрейворков, то эта задача из разряда примитивных. Shift-F12 в студии (R#?) покажет тебе абсолютно все вызовы конструктора и далее по цепочке можно раскрутить всё даже без необходимости запускать этот код. Если же используется DI фреймворк, то для того, чтобы понять что происходит необходимо запускать как минимум отладчик. При этом возникает несколько проблем.
IT>- этот код может находится в трудно доступном месте и для его выполнения потребуется создание определённого сценария, подготовки окружения и набора данных. IT>- проблема может возникать только в определённом окружении недоступном для отладки, а при создании искуственных условий проблема не воспроизводится.
IT>При наихудшем сценарии проблему всё равно придётся решать путём анализа кода, но в случае с DI фреймворком это на порядок сложнее.
С DI фреймворком ровным счетом ничего не меняется, только Shift-F12 нажимается не на конструкторе, а на названии класса. Дальше надо посмотреть лишь классы у которых в конструкторе есть параметр с типом Foo, и так же по цепочке раскручивается. Проблема надуманная, как мне кажется.
Здравствуйте, varenikAA, Вы писали:
AA>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов.
Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь.
AA>Взять теже фабрики, билдеры они тоже зло?
Да.
Если нам не помогут, то мы тоже никого не пощадим.
IT>Вот представь, у тебя есть простейший код: IT> if (b == null) IT> throw ... IT>Код кидает исключение. Твоя задача — найти причину. Если код без DI фрейворков, то эта задача из разряда примитивных.
Но в описываемой мною ситуации b не может быть null по определению, в этом случае контейнер бросает вполне читаемое подробное исключение, что такая-то завимисость в таком-то месте не может быть удовлетворена. Снова, возвращаемся к конфигурации и определяем эту зависимость — всё "в рамках".
Я вообще рассматривал бы именно такие проверки (на null) в другом контексте, когда мы так уж и знаем, откуда нам придут данные. Понятно, что на практике бывает по всякому, но это, как мне кажется, скорее все же не вопрос регистрации/использования дерева внутренних сервисов в рамках приложения.
Т.е. мы как бы о разных вещах говорим, я пытался описать именно преимущество ioc-контейнера как одного такого "реестра объектов" с конфигурацией правил их создания, определением реализаций где нужно, который умеет правильно очищать (через dispose) объекты с истекшим временем жизни, который строит дерево зависимостей и сам автоматически создаёт всё это дерево.
Т.е.:
IT>И здесь я опять вынужден повторить свой вопрос. Какую проблему решают DI фреймворки, которая по своей значимости способна перевесить вносимые в проект неудобства?
Я хотел примерно передать то, что выше описал (наверное, сумбурно). Я исходил из допущения, что разработчик а) знает о том, как работает ioc-контейнер в смысле того, что я выше описал; б) знает о конфигурировании сервисов в рамках приложения через ioc-контейнер. Но это же вроде классические для ioc-контейнеров понятия, т.е. ситуации:
IT>Если же используется DI фреймворк, то для того, чтобы понять что происходит необходимо запускать как минимум отладчик.
Здравствуйте, petroa, Вы писали:
IT>>Явно — это когда проверяется компилятором. P>Это идет вразрез с примерами, которые приводились, вроде "приезжает поломанным", или этот:
Он может приехать из какой-нибудь кастомной фабрики.
P>Честно, я не очень понял почему у нас вдруг всплыл компилятор Как-то сильно в сторону, в другую оперу.
Компилятор тут притом, что вот такие вещи "современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось." в коде без контейнера будут тупо ошибками компиляции. А уж эти современные контейнерные стек-трейсы — это какое-то издевательство над здравым смыслом.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, IT, Вы писали:
IT>>>Давай мы сразу определимся. Речь идёт не о DI как таковом, а о DI фреймворках и всяческих IoC контейнерах. Ты сейчас про что? S>>О них в том числе. Я просто действительно не понимаю что именно вас в них смущает: у меня два проекта, когда из первого, построенного на DI потребовалось перенести компоненты во второй (без DI) — не возникло никаких проблем, я просто руками конструировал все объекты.
IT>Т.е. чтобы перенести код пришлось его не слабо так переписать. Правильно?
Нет, не правильно: код остался полностью без изменений. Только вместо вызова вида:
var instance = serviceProvider.Get<SomeObject>()
Появилось
var db = new DbContext();
var config = new AppConfig();
var instance = new SomeObject(db, config);
Вот и всё различие. Внутренности SomeObject не менялись.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, microuser, Вы писали:
M>>Например есть у нас некий полезный класс у которого две зависимости:
IT>
M>>class Service
M>>{
M>> public Service(IDependency1 dependency1, IDependency2 dependency2)
M>> {
M>> }
M>> public void DoSomeWork()
M>> {
M>> //...
M>> }
M>>}
IT>
IT>Можно я ещё немного добавлю полезности? Спасибо!
IT>
IT>enum SourceType
IT>{
IT> Foo,
IT> ar
IT>}
IT>class Service
IT>{
IT> public Service(IDependency1 dependency1, IDependency2 dependency2, SourceType sourceType)
IT> {
IT> }
IT> public void DoSomeWork()
IT> {
IT> //...
IT> }
IT>}
IT>
IT>Как DI фреймворк разрулит ситуацию для разных sourceType?
Так не пишут при использовании DI. Нужно постараться уйти от зависимости которую нельзя разрешить автоматически. Как вариант SourceType можно попробовать перенести из конструктора в DoSomeWork.
·>Он может приехать из какой-нибудь кастомной фабрики.
Может, но это к вопросу контейнера не относится, ведь он также может приехать и при "ручном" создании.
·>Компилятор тут притом, что вот такие вещи "современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось." ·>в коде без контейнера будут тупо ошибками компиляции. А уж эти современные контейнерные стек-трейсы — это какое-то издевательство над здравым смыслом.
Но ведь это же плюс контейнера При добавлении в сигнатуру конструктора новой зарегистрированной зависимости она сразу будет передаваться при работе в рамках контейнера. Если не зарегистрирована — приложение упадёт с исключением при первом запуске (контейнер сразу строит граф зависимостей, не ждёт ничего в рантайме).
В "ручном" случае при манипулировании зависимостями (изменениями сигнатур конструкторов) мы постоянно меняем вызывающий код, обкладываемся проверками на null как в примере раньше и прочее. Стоила ли статическая проверка этого? Ну, может, в какой-то степени. Но на мой взгляд это никак не стоппер.
На деле имеем проблему с еще одной идиомой — конфигурация контейнера, о которой нужно знать (а знать сейчас и так дофига чего приходится). Это да. Но я пока пытаюсь сформулировать "мотивировочное решение" для использования ioc-библиотеки.
А стек-трейсы, они разные бывают, да... в джаве страшноватые, в дотнете я сейчас чаще встречаю вполне вменяемые.
Здравствуйте, Somescout, Вы писали:
IT>>Не-не-не. Нам твои DI фреймворки нафиг не нужны и нам без них хорошо. Покажи, что мы не правы, приведи код, где DI фремворки рулят. S>Ок, по сравнению со статическим синглтоном...
Где код?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Sharov, Вы писали:
S>>>Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае? IT>>Выше в теме об этом говорилось уже несколько раз. S>Я специально сделал акцент на слове поведение, а не на поиск и устранение причины.
Извини, видимо предполагал в твоём вопросе какую-то смысловую нагрузку, относящуюся к теме.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Sharov, Вы писали:
S> M>>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример. S> IT>Пусть передали не null, а поломаный объект. S> Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае?
Я не очень понял твой вопрос, но попробую потелепатить. Различие в том, что в случае с обычным кодом, можно найти откуда конкретно передали просто проанализировав код, find usages. В случае DI-фреймворка ты можешь лишь понять, что пришло из контейнера и всё, и без дебаггера очень сложно разобраться.
AA>>// Что тут неявного? ·>Непонятно кто от кого и как зависит.
AA>>Зато точка сборки приложения одна а не размазана по разным сборкам. ·>Просто делай то же самое, но без контейнера. Если я правильно разгадал твой код:
·>
·>var dbOptions = new DbOptions()
·> .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
·>var dbContext = new DbContext(dbOptions);
·>var mainService = new MainService(dbContext);
·>services.AddHostedService(mainService);
·>
·>Код внезапно стал проще — никаких лямбд, генериков, рефлексии, даункастов. Можно использовать IDE вовсю — find usages, declarations, использовать рефакторинги.
все разговоры в этой теме от банального непонимания, для чего придумали DI / IoC
ну так вот тебе надо ПРОТЕСТИРОВАТЬ, что схема базы данных, используемая твоим кодом, актуальна, например,
как ты с твоим этим кодом будешь писать код, который это проверяет?
с DI и EF InMemory Provider это будет где-то в три строчки
Здравствуйте, Буравчик, Вы писали:
Б>Таких параметров будет очень много (вся конфигурация приложения). А самое главное, при добавлении нового свойства конфигурации тебе придется менять конструкторы всех классов, которые "пропускают" через себя этот параметр. Аналогично при изменении какого-то компонента системы — если ты изменил реализацию (заменил БД на микросервис), то свойства станут другие, и опять же придется переделывать кучу кода по созданию объектов.
Б>А в проектах с DI ты делаешь изменение в одном месте — в composition root.
Как часто в своей жизни ты заменял БД на микросервисы?
Б>Чтобы убрать эту лапшу поступают просто — объекты делают глобальными (конфигурация, контекст БД и т.п.). И рано или поздно проявляются проблемы работы с глобальными переменными.
У меня проекту уже лет семь. Года четыре в продакшине. Глобавльные сетинги имеются. Сколько мне ещё осталось ждать до проявления проблем?
Б>Именно эта независимость частей системы придает системе гибкость, и это же добавляет сложности. Просто с ней работать нужно немного иначе.
Вопрос. Зачем нужна такая гибкость? Какую проблему это решает?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, C0s, Вы писали:
C0s>правила хорошего тона и clean code не зависят от размера проекта, и полезны сразу и систематически — до того, как проект начнёт внезапно разрастаться. более того, обычный подготовленный специалист это делает на автомате без дополнительных трудозатрат. а наличие неподготовленного — сам понимаешь, вроде и реальность, но является лишь организационной проблемой, а не инженерной.
У нас с тобой разные правила хорошего тона. Для тебя это непременное наличие какого-нибудь контейнера в проекте. Для меня — легко читаемый, легко изменяемый и легко остающийся легко изменяемым после изменений код.
IT>>Никто и не сравнивает DI или не-DI. Сравнивают DI фреймворк или не-DI фреймворк. C0s>раз уж мы тут, то DI-фреймворк важен тем, что для правильного применения сразу требует хорошего чувства объектного построения, проектирования надёжных и устойчивых моделей, а также инверсии зависимостей. без оного этого тоже можно добиться, но только палкой или кнутом. кроме того,
Ты ещё забыл про сферический конь в вакууме. Это тоже важно для правильного применения хорошего чувства объектного построения. В обычной жизни, особенно в кговавом энтегпгайзе все кони настоящие и быстро превращают вьюношей бледных со взором горящим либо в настоящих мужчин, либо в конченных догматиков с исковерканной DI судьбой.
C0s>не надо логировать постоянно, достаточно возможности увеличивать или уменьшать подробность логирования без останова процесса. нашли проблему пользователи — включаем логи, просим повторить, снимаем их и анализируем.
Кто тебе сказал, что оно опять повторится?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, takTak, Вы писали:
T>·>Да. И? Я ничего другого и не утверждал. Ты просто путаешь DI и DI-контейнеры. T>просто я никогда не пользовался никакими серверами приложений: контейнеры были для меня именно помощью в написании проложений, так чтобы получебнное прлижение просто тестировалось бы, T>если же тестов не предвидится, то и никакого смысла в том, чтобы композицию обьектов прозводить через DI, я не вижу, тут я на стороне того, кто эту ветку начал
Разберись в чём отличие DI и контейреров.
DI относится к юнит-тестированию — для инжекта моков.
Контейнеры относятся к апп-серверам. Там возникла идея "а давайте положим в контейнер транзакционные менеджеры, коннекты к базам, бины, и будем их извлекать и связывать автомагически".
Не мешай это всё в кучу.
T>ты начал использовать DI в контексте серверов приложений, причём, наверное, и без всяких тестов, имхо такой способ композиции приложения довольно прост, но какого-то выиграша от такого подхода , на самом деле, не видно
Видимо я просто не смог уследить за полётом твоей мысли, т.к. ты не понимаешь разницу между DI и контейнером. Вот тут ты пишешь одно, а строчкой ниже совсем другое.
этот код показывает, что ты ни разу правильно не написал тестового кода с использованием DI, правильно было бы так: Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надо
Мне надоело писать одно и то же. Ты игнорируешь эту разницу, тебя трудно понимать. Давай ты в следующем ответе опишешь своё понимание разницы DI и контейнеров, иначе смысла вести беседу я не вижу.
T>·>А вот контейнеры и фреймворки имеют прямое отношение к серверам приложений ибо идея пошла оттуда. T>я тебе уже в который раз повторяю, что идея пошла от юнит-тестирования, вероятно, создатели серверов приложений были в курсе того, что необходимо предложить пользователям возможность юнит-тестирования, и оно тут в списке первично
У тебя ещё похоже путаница с пониманием видов тестов.
На минуточку задумайся — юнит-тестирование это тестирование маленьких юнитов, когда все зависимости юнита мокаются. Использование контейнера это уже будет относится к интеграционным тестам, когда тестируются сразу несколько юнитов, сынтегрированных в одно целое с помощью контейнера.
Так что к юнит-тестам имеет отношение DI, а не контейнеры.
T>>>ты на своём коде покажи, "а вот дядя знает" в другом возрасте употребляют T>·>Ещё раз: вот тут
ванильный DI, без контейнеров и интерфейсы тоже не обязательны. T>то, что ты называешь "ванильным" , создаёт жёсткие зависимости между компонентами,
Не создаёт.
T>что делает практически невозможным изолированное тестирование
Вот с контейнером обеспечить изолированное тестирование гораздо сложнее. Ибо вот ты написал
Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надоvar mainService = Container.Resolve<MainService> ();
У тебя Container может неявно втащить много чего и изолировать сложно.
T>·>Да, уверен, просто ты не слушаешь. T>ты первый начал
Я слушаю, конечно, но ты мне ещё не рассказал ничего, что я не знаю.
T>>>т.е. ты делаешь DI без интерфейсов? ты точно свою ссылку из википедии прочитал? T>·>Да, прочитал. Для DI/CI интерфейсы не нужны. И контейнеры тоже. А интерфейсы _нужны_ только для так называемого частного случая Interface Injection (который в этом обсуждении ни разу не упоминался). И ты прочитай, рекомендую. T>этот , как ты говоришь , "частный случай" — для меня единственно возможное и целесобразное применение , иначе смысла просто нет
Ты издеваешься что-ли? Ты статью так и не прочитал. И нифига не понимаешь что такое Interface Injection.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Буравчик, Вы писали:
Б>Чтобы реализовать переопределение зависимостей без использования контейнера создаем TestAppRoot (наследуется AppRoot), в котором переопределяем несколько create-методов. Б>
Ты почти дошел до цели, остался последний шаг. TestAppRoot не нужен! Теперь для AppRoot надо ТОЖЕ применить IoC и через DI заинжектить туда зависимости, которые ты хочешь подменять при интеграционном тестировании.
Т.е. main-метод реального приложения будет выглядеть так:
new AppRoot(new RealDb(), new RealFilesystem(), new SystemClock()...).run();
А для теста
new AppRoot(new MockDb(), new MockFilesystem(), new MockClock()...).run();
Другими словами, Composition Root это вовсе не одна большая простыня создания всего, а это тоже код, который тоже можно структурировать обычными способами. А значит части Composition Root так же можно переиспользовать в тестах и в прод-коде.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, takTak, Вы писали:
T>>>я тебе ещё раз говорю: ты со своей явой ни туда смотришь, контейнеров под дотнетом сотни, T>·>И большинство из них позаимствовано из Явы. T>я даже не знаю, о чём ты.., ты , наверное, за словом "контейнер" понимаешь лишь j2ee container, ну так вот "контейнер" — это всего лишь "контейнер", не более, современные IoC контейнеры в отличных от явы языках очень легковесны
Раз не знаешь, так спрашивай, а не говори очередную глупость. В java полно контейнеров, всяко-весных. Из легковесных — pico, guice, dagger, например. Притом тому же pico лет 20 уже... современно! жив ли он? Не уверен.
T>>> и никакими серверами приложений там даже близко не пахнет, как и в многих других языках T>·>IIS же. T>это скорее веб север типа tomcat или apache
Тут терминология нечёткая, конечно. Но всё таки тот же kestrel далеко от iis.
T>>>тебе твоя ява застилает глаза, контейнер не обязан быть сложным T>·>Тебе твой дотнет застилает глаза, контейнер не обязан быть. T>легковесный IoC контейнер безумно облегчает жизнь
Пока не научишься код нормально писать.
T>·>Ты уж определись. Либо "мне трогать не надо", либо "регистрирую то, что меня интересует". Т.к. ты либо явно должен указать, что тебя интересует, либо это будет описано где-то неявно и тогда будет лезть что попадётся. T>так это тоже элементарно: если то, что по дефолту подтащится, для инициации теста прокатит, но в самом тесте вызвано не будет, то мне достаточно подменить только то, что я собираюсь тестировать
Не понял. Если ты подменишь то, что собираешься тестировать, то ты будешь тестировать подмену.
T>·>И для них интерфейсы использовать не нужно (но можно, к DI это ортогонально). ЧТД. T>смысл использования интерфейса в оо — это double dispatch,
Чё? Ты точно знаешь, что такое double dispatch? Или опять путаешь термины?
T>и если у тебя имеется код, который десятилетиями не меняется, то и смылся ни в тестах ни в интерфейсе, ни в DI для него нет
Это высказывание ложно по стольким параметрам, что даже лень начинать.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
T>>и если у тебя имеется код, который десятилетиями не меняется, то и смылся ни в тестах ни в интерфейсе, ни в DI для него нет ·>Это высказывание ложно по стольким параметрам, что даже лень начинать.
ну так и помолчи, раз тебе уже давно нечего сказать
T>>Если кода мало и он примитивный это не означает, что он какой-то неправильный, тебе просто непривычно после контейнеров-то.
если это не сарказм конечно, то уточню:
если кода мало (измеряется в количестве стейтментов, выражений, цикломатической стоимости),
то это скорее всего значит что он хороший
кратчайший код — вероятно лучший
бывают исключения, когда несмотря на краткость он трудно понимаемый
но если он ещё и примитивный, то это вообще критерий хорошего кода
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
МП>>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
VD>А вот это точно полная чушь. Прекрасно рефакторю код напичканный DI с помощью Райдера. Никаких проблем нет. Наоборот мощные рефакторинги Райдера помогают в этом.
я райдером не пользовался до сих пор
у него есть фичи сверх решарпера?
в недрах темы я отчасти раскрыл что имею в виду:
использование элементов класса может быть неявным через IoC-фреймворк (сейчас понимаю лучше формулировать так)
увидев в IDE 0 количество использований — придётся озираться, а не использует ли его IoC-фреймворк, а не удалить сходу
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Sharov, Вы писали:
S>Это именно проблема IoC/DI, т.к. он может конфигурироваться из файла. При D. Inversion и без D. Injection у нас все равно как минимум S>одно использование типа в коде будет (кто-то же должен его создать?). А при создание может конфигурироваться хрен знает где S>и хрен знает как, и студия может об этом вообще ничего не знать. Вот и показывает, что тип нигде не используется, хотя S>он явно прописан в конфиге как зависимость.
Что за "логика"? То что что-то там может — это не значит, что это является причиной. Ты создавая экземпляр можешь значительно больше чем любой фрэймворк. У нас вот проекте конфигурация из файла не используется в принципе. Вся конфигурация в коде.
Если у тебя в коде интерфейс, ты автоматически получаешь большую неопределенность нежели при работе с конкретным типом, так как ты работаешь с абстракцией. Работу с абстракцией выбираешь ты сам. Если этот выбор сделан осознанно и грамотно, а не потому, что "все так делают", то ты преследовал какую-то цель. И вот эта вот цель подразумевает подмену типа в рантайме. Используя DI ты волен создавать не только экземпляры конкретных типов, но и даже делать их все sealed, чтобы гарантировать, что они не могут быть подменены в рантайме.
Так что не говори чушь. Причина именно dependency inversion. Ты путаешь причину со следствием.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, petroa, Вы писали:
p> ·>То добавить dao1 в Svc2 никаких дополнительных правок не потребует. p> Разве, а конструктор? Впрочем, не важно.
Да. Просто добавляешь и introduce parameter, IDE сама всё сделает.
p> ·>Зато в Dao1 добавить svc1 у тебя просто не получится скомпилировать. p> Не получится, да. А при использовании ioc-контейнера может и получиться
Так это плохо, т.к. у тебя возникнут циклические зависимости между слоями приложения.
p> (смотря как конфигурация описана).
По умолчанию у тебя один контекст со всеми зависимостями, глобальая переменная. Разделять контекст на части в контейнерах очень тяжело и то что ошибки стали рантайм — ещё сильнее бьёт.
p> ·>Далее по тексту начинался новый параграф.. я не понял в чём "но". p> Ну, перед этим по тексту ) Я по смыслу имел в виду — в "автоматическом" управлении зависимостями (контейнер) vs "статическом" есть свои плюсы и минусы, и возникновение ошибки в рантайме минус, конечно — но нужно учитывать, что "рантайм" тут не тот, который будет долго ждать своего часа.
В том то и дело, увидеть эти ошибки — нужно ждать часа. В статическом ошибки у тебя тут же подсвечиваются красным в IDE.
p> Однако, при неких удобствах они, за исключением некоторых специфичных применений (описанных в т.ч. VladD2) ухудшают качество дизайна, и это необходимо понимать. Это так, немного банальностей в качестве резюме для себя.
Единственное неоспоримое удобство которое в дискуссии было описано — если код пишешь в нотепаде, то нужно нажимать меньше кнопок.
Поэтому я считаю контейнеры пережитком прошлого, когда код писать было сложно.
Здравствуйте, Буравчик, Вы писали:
Б>Рискую быть непонятым, но скажу, что DI это даже круче — в некотором роде это ФП-стиль построения большого приложения. Как в ФП мы передаем замыкание ("настроенную" функцию), так в DI мы передаем зависимость (уже "настроенный" сервис).
Так за все приходится платить. Разбираться в коде созданном на базе большинства DI действительно сложнее. По уму нужно делать статический DI, который резолвил бы зависимости во время компиляции. Это сняло бы много проблем. Но современные мэйстрим-языки на это не особо рассчитаны. Вот и появляются строковые конфиги. Ошибки при загрузке. Непонимание того почему тут подсунули некоторый тип, а не иной. И т.п.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD> Б>Рискую быть непонятым, но скажу, что DI это даже круче — в некотором роде это ФП-стиль построения большого приложения. Как в ФП мы передаем замыкание ("настроенную" функцию), так в DI мы передаем зависимость (уже "настроенный" сервис). VD> Так за все приходится платить. Разбираться в коде созданном на базе большинства DI действительно сложнее. По уму нужно делать статический DI, который резолвил бы зависимости во время компиляции. Это сняло бы много проблем. Но современные мэйстрим-языки на это не особо рассчитаны. Вот и появляются строковые конфиги. Ошибки при загрузке. Непонимание того почему тут подсунули некоторый тип, а не иной. И т.п.
В Скале есть неявные параметры которые это вроде как позволяют. Но по-моему это даже хуже. На демках это круто, но чуть дальше в лес и пришли...
Мне кажется, что зависимости это важная часть дизайна и к ней нужно подходить осторожно, описывать явно. Нахаляву получается отличная compile-checked документация для кода. А все эти контейнеры и неявности — экономия на строчках кода (лишь за счёт того, что мы называем конфиги не кодом!) без каких-либо улучшений качества.
Б>Абстракции позволяют создавать нам сложные системы. Через абстракции не надо продираться, их надо принять (понять), не залезая внутрь. На то они и абстракции.
В неком идеальном мире — да, и то при одном важном условии: общее количество абстракций, с которыми приходится жонглировать, не превышает некоего числа N. Которое, кстати, совсем невелико.
Но мы живем в неидеальном мире.
1. Создаваемые на скорую руку абстракции чаще всего leaky.
2. Конкретные реализации абстракций содержат ошибки.
3. Поиск ошибок в большом количестве компонентов — сложная задача.
4. Протестировать компонент обычно сложнее, чем функцию.
5. В большинстве случаев при наличии значительного числа компонентов связность системы начинает расти. И почти всегда приходит к схеме "все зависит от всего", оно же копромонолит.
Б>Про DI добавлю, что когда используешь DI тебе приходится разделять код на независимые компоненты. Начинаешь мыслить компонентами, что-ли. Б>А если "просто писать" код не задумываясь об этом, то код получается связанным, и становится еще труднее понять, что и как работает.
Связность всегда связность. Перенося связность на более высокий уровень (от связности на уровне функций к связности на уровне компонентов) может возникать неоправданое усложнение. К примеру, мне нужно всего лишь сконвертировать дату/время. Но чтобы это сделать, приходится затаскивать огромный "компонент", который чуть ли не через HTTP календарь выставляет. А, еще пример из жизни, — openssl не поддерживает определенный вариант BLAKE2. Поэтому разработчик, ничтоже сумняшеся, впупыживает на все запущеные в компании микросервисы еще и NaCl (который искомый вариант хеш-функции поддерживает).
Здравствуйте, VladD2, Вы писали:
VD>У ТС DI не используется по причини не любви к ним. А все остальное попытка подобрать аргументы к исходной установке.
ТС пришел в проект,где DI\IoC во весь рост и не понимает кому и зачем оно нужно. Ибо столкнулся с трудностями при работе --
удалил якобы не нужный класс (нигде не использовался), а он использовался, только сильно не явно.
VD>В-третьих, люди использующие DI должны понимать что происходит и производить поиск другими методами. В нашем случае, например, ищутся вызовы "...Register<НужныйТип>(...". Сам DI не знает что подсунуть вместо интерфейса. Стало быть без регистрации не обойтись. Ну, а если это конкретный sealed тип и в регистрации он не замечен, то он тупо создается через DI.
Суть в том, что искать надо вовсе не в исходниках, что поначалу может сильно удивлять.
Здравствуйте, Sharov, Вы писали:
S>Что значит "статический DI" ? Он из коробки такой, а ломает его IoC, который вообще может из файла конфигурироваться.
Из коробки его нет. Все DI — это IoC. Обратное не верно. Не пори чушь. Причем обязанности поддерживать конфигурацию на текстовых файлах у них нет. Это твои домыслы. Это не более чем особенность реализации.
По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей и автоматическое создание объектов в последовательности зависимостей. Так вот этот процесс можно осуществлять как в рантайме, так и во время компиляции. Последнее дает больше контроля и избавляет от кучи проблем.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
S>Как-то продивинуто . Зачем тут полноту по Тьюрингу поминать? Вся суть в том, что DI\IoC не предполагает S>явно создания объектов программистам, но иногда это необходимо.
Создать объекты можно безо всяких там DI/IoС.
Как уже было замечено выше, "контейнер" (или composition root) — это способ назвать код "конфигурацией" и вынести его туда, где тестировать его становится сложно, а значит, (барабаны), не нужно.
Был тут уже выше пример про то, что хочется назвать определенную сборку "продакшном", и под этим соусом избежать тестирования.
Здравствуйте, VladD2, Вы писали:
VD>С памятью проблем нет, так как 64битный.
Решарпер что в Райдере, что в студии все равно сидит в своем собственном 64-битном процессе.
VD> А так почти тоже самое.
Тоже, да не совсем. СОМ-ядро студии, увешанное блокировками, как бродячая собака блохами, начиная с некоторого размера проекта требует для работы с ней или сноса решарпера, или обретение дзена и спокойное ожидание когда внезапно подвисшая прям в процессе набора кода студия отвиснет. У Райдера этой проблемы нет.
Здравствуйте, Sharov, Вы писали:
S>Это именно проблема IoC/DI, т.к. он может конфигурироваться из файла.
А может? Я уж даже забыл о этой мегафиче и не мог понять о какой замене кода на конфигурацию рассказывает СкайДенс.
Эта хрень была в самых первых DI фреймворках. Потом быстро появилаась возможность конфигурации из кода, а в свеженьком DI от MS, к примеру, такой фичи просто нет. Совсем.
Так что забудьте вы уже про этот бред воспаленных мозгов бла-бла-архитектов, оно никому с адекватным восприятием реальности не нужно.
Здравствуйте, Somescout, Вы писали:
IT>>Вроде как код собираешься приводить ты, а не я. S>Вы б документацию к своему LinqToDB с таким же энтузиазмом пилили, как балаболили на форуме.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>DI это и есть проект с сервис-локаторами и синглтонами, просто в несколько более запутанном виде.
Все-таки не совсем. В случае SL на вход принимается сам контейнер и далее через него получаются соотв.
типы. При DI мы получаем только нужны нам типы, про контейнер нам знать вообще не надо. Т.е. при SL
выпилить IoC контейнер будет сильно труднее, чем при DI.
Здравствуйте, Sharov, Вы писали:
VD>>>По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей и автоматическое создание объектов в последовательности зависимостей. НС>>Нет. Основная фича DI контейнеров — управление созданием и уничтоженим экземпляров сервисов, и рантайм биндинг этих экземпляров к потребителям (слово injection в названии четко и недвусмысленно определяет что такое DI). Что то там рассчитывать на графе зависимостей — совершенно необязательная фича, которой может просто не быть. S>А как узнать тогда кто и от чего зависит, что сделать инъекцию?
По тому какие параметры в какой конструктор передаются.
S>Т.е. каков порядок создания объектов тогда, без графа?
В порядке исполения wiring-кода.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sharov, Вы писали:
S>Все так, конечно, только вопрос кто и как формирует этот граф -- программист через new (не важно, S>фабрика, билдер и проч. и проч.) или некий компонент, через рефлексию, например.
Этот вопрос никак не вляет на суть того что такое DI контейнер, и является деталью его реализации.
AA>Да не спорю я что явное лучше не явного, но иногда динамическое связывание просто необходимо.
Когда необходимо, тогда и нужно использовать. А заранее создавать объекты из собственного же репозитория путем выноса графа зависимостей куда-то в отдельное место ("composition root"), да еще и на другом языке (XML), — это без меня.
Здравствуйте, Sharov, Вы писали:
НС>>Ничего интересного там не скрыто, все что имеет отношение к делу там есть. Если что то непонятно — спрашивай. S>Да, непонятно, что там за ... скрыто.
Где скрыто? Поконкретнее.
S> Можно увидеть полный пример?
Полный пример чего? Я тебя не понимаю.
S>Замечательно, но про "DI как частный случай SL" где прочитать?
Голову включить и логику в ней. DI контейнер полностью подходит под определение SL. Значит DI является SL. При этом, очевидно, не каждый SL является DI контейнером. Отсюда следует, что DI является частным случаем SL.
Здравствуйте, ·, Вы писали:
·>Параметр объявляет тип, а инжектится инстанс. НС>>И? При вызове Resolve указывается тип, а возвращает он инстанс. ·>Типы тут вообще непричём.
Здравствуйте, ·, Вы писали:
·>Отделение логики создания компонент от логики их использования и явное описание их взаимосвязей без потери Compile-time проверки зависимостей.
Логика отделяется при помощи DI FW, запрет null в C# 8 выполнит проверку в режиме компиляции.
·>Родная поддержка навигации по коду и рефакторингов в IDE.
Сложно судить о ваших предпочтениях, но меня из коробки vs 2019 устривает, можно найти любую ссылку, причем поиск учитывает положение курсора.
·>Позволяет строго структурировать зависимости и избегать ошибок случайного создания плохих зависимостей.
что за плохие зависимости?
·>Живая документация по структуре приложения.
Особой разницы нет, разве что ответственность за создание экземпляров ложится целиком на кодера вместо отлаженной и удобной библиотеки.
Итог: больше кода и ручное управление. Оба момента давно уже признаны признаком плохого кода.
НС>>Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн..
НС>Да мало ли что где клеймят.
Я service locator позором не клеймил, ибо с какого угла ни посмотри, но некий способ развернуть имя в объект в любом случае должен быть.
НС>А я уже отвечал, что в 2021 году почти никто уже не видит смысла в этом и в свежих контейнерах такой фичи просто нет. Так какой смысл постоянно переводить разговор на это дерьмо мамонта?
И как же делают в 2021 году?
А код, который был до 2021, тоже уже весь переписали?
НС>Конечно. Но так редко бывает, что у сложных проблем есть простые решения.
Напротив, почти всегда сложная проблема является следствием переплетения нескольких более простых. Надо лишь уметь декомпозировать сложную проблему, найти корень (или корни) зла, и внести нужные изменения. Но это требует хорошего не только знания предметной области, но еще и широкого кругозора, умения мыслить (не шаблонами), разбираться в задаче, в коде, в том, что на самом деле хотелось получить (а не в том, что закодировано).
МП>>С моей профессиональной точки зрения DI фреймворки не нужны.
AA>Какой велосипед предлагаете взамен?
так а зачем вообще делать dependency injection?
не как паттерн проектирования — чтобы развязать циклическую зависимость сборок,
а для инициализации новых объектов
на последнем проекте я видел, что она применялась вообще для большинства классов, у которых даже не было нормальных конструкторов
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
vsb>Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
нормальные обычные конструкторы, ну либо статические конструкторы, если нужен пул объектов...
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
МП>>кто-то может популярно расписать преимущества либо природу явления популярности?
F2>Я недавно спрашивал подобное: http://rsdn.org/forum/java/7835059.1
Здравствуйте, Министр Промышленности, Вы писали:
vsb>>Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
МП>нормальные обычные конструкторы,
не понял какие конструкторы у тебя нормальные но DI не накладывает никаких ограничений на это.
МП>ну либо статические конструкторы,
их DI отменяет или что?
МП>если нужен пул объектов...
И?
vsb>>>Не очень понятно, чем ты предлагаешь их заменить. Я знаю несколько альтернатив: глобальные переменные; реестр объектов. Обе эти альтернативы мне не нравятся и имеют серьёзные минусы. В случае с DI минусов лично я не вижу. С тем, чтобы DI затруднял распутывание кода, я не сталкивался. Возможно ты сможешь пояснить, что именно ты имеешь в виду? Я, правда, пользуюсь Java.
МП>>нормальные обычные конструкторы, GIV>не понял какие конструкторы у тебя нормальные но DI не накладывает никаких ограничений на это. МП>>ну либо статические конструкторы, GIV>их DI отменяет или что?
вопрос был в разрезе целесообразности использования DI
если можно использовать нормальные конструкторы, зачем там применять DI?
или даже зачем заменять нормальное конструирование дополнительными DI сборками и конфигурированием этого всего
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>Они затрудняют распутывание кода,
Нет. Зачастую создание большинства объектов в одном месте.
Проект с сервис-локаторами, синглтонами и прочим запутанее, чем проект с DI. Хотя бы потому что больше boilerplate кода.
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
МП>>вопрос был в разрезе целесообразности использования DI МП>>если можно использовать нормальные конструкторы, зачем там применять DI? МП>>или даже зачем заменять нормальное конструирование дополнительными DI сборками и конфигурированием этого всего
M>Как вообще можно писать бизнес приложения без DI ума не приложу
подозрительное заявление
M>покажите пример, а мы посмотрим.
это не вполне легально по нынешним временам
но всё что я сам писал было без DI кроме мест в проектах где это было уже мне навязано через легаси
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>а реальная необходимость бывает редко и всего в 2-3 местах системы
Очевидно, что DI это реализация принципа инверсии зависимостей.
Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
M>Нормальное DI как раз и работает через конструкторы. Как вообще можно писать бизнес приложения без DI ума не приложу, покажите пример, а мы посмотрим.
Для банковского или шире, финансового ПО, где требования меняются часто, может и критично. Для любых других приложений может и не надо.
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
инструмент должен решать задачу.
DI не везде нужен, особенно в вин-формах, имхо.
но вот тебе пример из недавнего с нашего проекта.
есть сервис, пусть будет MyService, который делает достаточно долгий запрос за статическими данными.
есть интерфейс, понятное дело, IMyService.
этот сервис используется в большом количестве мест проекта. через интеферйс, понятное дело, не напрямую создавая инстанс.
запустив нагрузочное тестирование, мы поняли, что мы упираемся в производительность этого сервиса, и если бы мы закешировали данные, получили бы приличный прирост производительности.
дальше, мы создали над MyService обычный декоратор, аля MyServiceWithCache, который так же реализует IMyService интерфейс. всё что там нужно было, поменять регистрацию в DI в одном месте.
дальше, везде подхватилась новая реализаций, реализация нашего декоратора. приложение взлетело.
это лишь один и маленький пример. я могу привести много больше
МП>если можно использовать нормальные конструкторы, зачем там применять DI? МП>или даже зачем заменять нормальное конструирование дополнительными DI сборками и конфигурированием этого всего
тебе известны такие понятия как SOLID, и, конкретно, D из этой группы? или что такое DI как паттерн и для чего он вообще нужен?
Здравствуйте, Министр Промышленности, Вы писали:
МП>>>нормальные обычные конструкторы, МП>>>ну либо статические конструкторы,
МП>вопрос был в разрезе целесообразности использования DI
до этого момента, когда ты спрашивал про DI-фреймворки, всё было правильно,
а сейчас съехал в сторону
передача зависимостей в конструкторе это такое же средство реализации DI
Это меня на текущем проекте просто достало. Чуваки с золотыми молотками суют это уродство во все щели — и куда надо, и куда не надо (на самом деле почти никуда не надо). Гора интерфейсов, за которыми найти не возможно просто ничего. Тьфу.
vsb>У меня есть один объект. Например назовём его UserDao. И есть второй объект. Назовём его AuthService. Для AuthService нужна ссылка на UserDao. Каким образом он эту ссылку получит?
цитирование
vsb>Сконструировать он его не может. Во-первых я просто не хочу, чтобы в системе было много UserDao, например там может быть кеширование или ещё что угодно. Во-вторых для создания UserDao мне нужно соединение к БД, возможно ещё что-то. Передавать всю эту кучу скопом в AuthService (и в любой другой объект, которому нужен UserDao) это маразм. vsb>Если я правильно понял про "статические конструкторы", ты предлагаешь сделать что-то вроде UserDao.getInstance(). То бишь реестр объектов, причём плохой и неудобный реестр. Так делать можно, но при этом возникает уйма проблем:
vsb>1. Такой код сложно тестировать. Если я хочу, чтобы AuthService принимал фейковый UserDao, мне нужно вызывать в тесте UserDao.setInstance(testUserDao). При этом мне в принципе нужно помнить при тестировании AuthService про все его зависимости. А ещё надо не забыть по окончании работы теста убрать все эти тестовые переменные, иначе это может поломать другие тесты. Ещё возникают вопросы с многопоточностью. В общем проблем много. Решать как-то их, наверное, можно, но неудобно.
vsb>2. Такой код сложно переиспользовать. Если мне всё же понадобится в реальном коде другой UserDao конкретно для AuthService, то это будет просто невозможно сделать в рамках данного подхода.
vsb>3. Непонятно, собственно, к чему мы пришли в итоге. У нас есть вызов UserDao.getInstance(), который возвращает что-то. Что он возвращает? Неизвестно. Кто-то там ему сделал setInstance раньше, его и возвращает. Какая разница с DI в плане понимания работы кода? Никакой разницы.
vsb>4. Сложно понять, от чего зависит AuthService. Нужно внимательно читать его код, изучать список импортов. В случае с DI ты просто смотришь на параметры конструктора и всё как на ладони. Это бывает удобно.
var userDao = DatabaseHelper.GetUserDao();
var authService = new AuthService(userDao);
МП>>кто-то может популярно расписать преимущества либо природу явления популярности? МП>>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
САД>инструмент должен решать задачу. САД>DI не везде нужен, особенно в вин-формах, имхо.
да, спасибо, я хотел видимо именно это подтверждение
САД>но вот тебе пример из недавнего с нашего проекта. САД>есть сервис, пусть будет MyService, который делает достаточно долгий запрос за статическими данными. САД>есть интерфейс, понятное дело, IMyService. САД>этот сервис используется в большом количестве мест проекта. через интеферйс, понятное дело, не напрямую создавая инстанс. САД>запустив нагрузочное тестирование, мы поняли, что мы упираемся в производительность этого сервиса, и если бы мы закешировали данные, получили бы приличный прирост производительности. САД>дальше, мы создали над MyService обычный декоратор, аля MyServiceWithCache, который так же реализует IMyService интерфейс. всё что там нужно было, поменять регистрацию в DI в одном месте. САД>дальше, везде подхватилась новая реализаций, реализация нашего декоратора. приложение взлетело.
ок, хотя можно было это решить и без DI фреймворка в проекте
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
МП>>Они затрудняют распутывание кода, МП>>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>>и это не перекрывается гибкостью подстановки mock-объектов
IT>Распутывание кода и рефакторинг в основном затрудняет тотальное использование интерфейсов, которые так любят использовать в DI фреймворках.
ну это захламляет, да, но достаточно на методе сделать "Find Usages advanced"
а вот неявное вызывание конструктора реально сбивает с толку
IT>В результате имеем — неумеха с золотым молотком пишет со всем старанием бизнес логику в стиле универсального всемогутера. IT>Какждый раз когда я вижу в проекте использование DI фреймворков и IoC контейнеров у меня возникакет именно такое впечатление.
да, такое же впечатление
только я даже не уверен что авторы старали всемогутор, а не штамповали мантры
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Somescout, Вы писали:
МП>>кто-то может популярно расписать преимущества либо природу явления популярности? МП>>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
S>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI?
А почему оставим-то? Это очень интересный вопрос.
Или получается, что DI полезен только вот в таких "хитрых" архитектурах?
А если что-то пилить с нуля, то можно и обойтись без DI?
Здравствуйте, TG, Вы писали:
S>>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI?
TG>А почему оставим-то? Это очень интересный вопрос.
Потому что пример абстрактный.
TG>Или получается, что DI полезен только вот в таких "хитрых" архитектурах?
Что "хитрого" в такой архитектуре?
TG>А если что-то пилить с нуля, то можно и обойтись без DI?
Обойтись можно без чего угодно, вопрос в удобстве. Если хотите, например, можете прокидывать базу (или сервис доступа к данным, или любую другую службу) сверху вниз, или конструировать на месте, или использовать статические репозитарии — всё можно. Только нафига усложнять себе же жизнь?
Здравствуйте, TG, Вы писали:
TG>Фраза "Допустим мне в глубинах компонентов понадобился доступ к базе" намекает, что при проектировании несколько подзабыли принципы SOLID, разделение на слои и т.д.
А можете объяснить — в чём именно вы увидели нарушение SOLID?
TG>И если сделано это осознанно, то было бы опять же интересно посмотреть на конкретный кейс.
Ок, простой пример: на веб странице компонент должен выводить имя текущего пользователя. Он должен его откуда-то получить: либо вы при рендеринге каждой страницы прокидываете эту информацию ему вручную, либо компонент так или иначе (через DI, репозиторий, синглтон) получает эту информацию сам.
Здравствуйте, varenikAA, Вы писали:
AA>Здравствуйте, Министр Промышленности, Вы писали:
МП>>а реальная необходимость бывает редко и всего в 2-3 местах системы
AA>Очевидно, что DI это реализация принципа инверсии зависимостей. AA>Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
Конкретный пример пож-та, чтобы спор был не абстрактным.
Здравствуйте, IT, Вы писали:
IT>А раз так, то как же можно получить информацию о кастомере без DI фреймворка и пары IoC контейнеров? Никак не можно.
Кстати, а если действительно никак не можно — например информация о кастомере разбросана по нескольким сервисам?
Здравствуйте, Poopy Joe, Вы писали:
PJ>Здравствуйте, Somescout, Вы писали:
S>>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI? PJ>Отличное описание. DI это костыль, когда лень думать об архитектуре, но не лень потом все это разгребать и отлаживать.
Интересно было бы посмотреть на вашу архитектуру которая делает DI не нужным.
M>Пока что вы описали чистую архитектуру, а я спрашивал не как вы организуете слои, а как передаете зависимости в конкретные классы. M>Например есть у нас некий полезный класс у которого две зависимости:
M>
M>class Service
M>{
M> public Service(IDependency1 dependency1, IDependency2 dependency2)
M> {
M> }
M> public void DoSomeWork()
M> {
M> //...
M> }
M>}
M>
M>Как вы предлагаете создавать экземпляр этого класса? Руками писать new Service(new Dependency1(...), new Dependency2(...)), при чем у каждой зависимости есть свой конструктор с другими зависимостями.
ну в общем да, именно так и писать
если инициализация будет объемистой, то вынести в инициализирующий статический helper метод и везде использовать его
M>А если потом в этой цепочке где то поменяется сигнатура конструктора — придется исправлять все места где он вызывается. Вопрос зачем такие сложности, если задачу создания объектов можно поручить фреймворку который сделает это все совершенно прозрачно?
если поменятся сигнатура, то об этом напомнит даже не компилятор, Решарпер до компилирования, или даже просто IDE
и модификация окажется механической работой, не затрачивающей самый главный ресурс — моральные силы разрабочитка
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
M>>Пока что вы описали чистую архитектуру, а я спрашивал не как вы организуете слои, а как передаете зависимости в конкретные классы. M>>Например есть у нас некий полезный класс у которого две зависимости:
M>>
M>>class Service
M>>{
M>> public Service(IDependency1 dependency1, IDependency2 dependency2)
M>> {
M>> }
M>> public void DoSomeWork()
M>> {
M>> //...
M>> }
M>>}
M>>
M>>Как вы предлагаете создавать экземпляр этого класса? Руками писать new Service(new Dependency1(...), new Dependency2(...)), при чем у каждой зависимости есть свой конструктор с другими зависимостями.
МП>ну в общем да, именно так и писать МП>если инициализация будет объемистой, то вынести в инициализирующий статический helper метод и везде использовать его
M>>А если потом в этой цепочке где то поменяется сигнатура конструктора — придется исправлять все места где он вызывается. Вопрос зачем такие сложности, если задачу создания объектов можно поручить фреймворку который сделает это все совершенно прозрачно?
МП>если поменятся сигнатура, то об этом напомнит даже не компилятор, Решарпер до компилирования, или даже просто IDE МП>и модификация окажется механической работой, не затрачивающей самый главный ресурс — моральные силы разрабочитка
Лишняя работа, больше boilerplate кода, как следствие ниже читаемость, а плюсы то есть?
Здравствуйте, Sinclair, Вы писали:
S>Ну это же прекрасный пример. Через три-четыре релиза у нас в топ жалоб выезжает "ваше приложение нещадно тормозит", привлекается команда высокооплачиваемых экспертов, которые после трёх месяцев расчистки конюшен констатирует очевидное: "у вас там каждая кнопка в гуе считает, что ей нужно лазить в базу/сторонний сервис/ещё куда-то, в итоге для построения одного экрана открывается 27 коннектов к базе, и исполняется более трёхсот SQL-запросов. Из них треть дублируют друг друга, а половина, хоть и не совпадает, может быть покрыта одним из более широких запросов". Рекомендация экспертов: "чётко разделить фазы построения гуя, доставая данные из базы минимальным количеством запросов и передавая визуальным компонентам готовую viewModel".
Здравствуйте, microuser, Вы писали:
M>Интересно было бы посмотреть на вашу архитектуру которая делает DI не нужным.
По мне, так DI не просто не нужен, а только вредит. У кода выше из-за DI сразу несколько проблем, причем на ровном месте: требуется состояние, неявная параметризация функций, смешивание бизнес-логики и сторонних эффектов, неявная зависимость от внешнего поведения.
Серебряной пули тут нет и каждые случай "необходимости" DI решается по разному, но при этом оставляя бизнес-функции чистыми. В общем случае все сводится к тому или иному виду композиции, а функция в параметрах получает то, что в случаи DI запрашивает сама из внешних источников. В языках где есть частичное применение параметров для этого вообще ничего специального делать не надо, но в том же c# можно возвращать Func вместо результата. Для логгера можно использовать монаду, пример.
Здравствуйте, microuser, Вы писали:
M>Лишняя работа, больше boilerplate кода, как следствие ниже читаемость, а плюсы то есть?
Это все как раз про DI. Особенно читаемость — она там где-то в районе нуля.
Плюс лишние тормоза и еще потеря статической типизации в точке получения объекта.
Здравствуйте, Sinclair, Вы писали:
S>То, что в недрах гуя нет простого доступа к DBConnection — это хорошо, а не плохо. Так что нам не нужно средство, которое даёт эту невыносимую лёгкость сделать "а шандарахну-ка я тут запросец в базу".
Плюс адин. Провоцирует на повышение связности на ровном месте. В результате на выходе Big Ball of Mud.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, microuser, Вы писали:
M>>Лишняя работа, больше boilerplate кода, как следствие ниже читаемость, а плюсы то есть?
ARK>Это все как раз про DI. Особенно читаемость — она там где-то в районе нуля. ARK>Плюс лишние тормоза и еще потеря статической типизации в точке получения объекта.
Как это читаемость будет меньше при большем количестве ничего не делающего кода?
Еще раз говорю, покажите реальный проект без DI или хотя бы CRUD заготовку.
Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
Нужны. Но значительно реже, чем их реально применяют.
МП>Они затрудняют распутывание кода,
Да
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>и это не перекрывается гибкостью подстановки mock-объектов
Да
МП>но обнаруживаю ярых адептов этого всего. МП>уже и в вакансиях суют такое требование
Это всегда бывает. Когда хорошая мысль залетает в пустую голову, она занимает там всё свободное место.
DI фреймворк — это не универсальное решение всего. Это инструмент, который в каких-то случаях упрощает жизнь
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
Преимущества описаны неоднократно и много где. Но там обычно забывают описать недостатки, на которые Вы как раз обратили внимание.
Природа популярности — хайп и увлечённость.
На мой взгляд, такие фреймворки хорошо применять тогда, когда есть реальная вероятность изменения конфигов этого фреймворка после поставки продукта.
Или в прнодукте возможны несколько конфигураций.
Например, в уже рабочем и установленном продукте поменять логгирование. Или подключить другой модуль платежей.
Или поставлять продукт разным заказчикам с кастомизированной бизнес-логикой.
В общем, те же ситуации, когда нужны по сути плагины.
Здравствуйте, microuser, Вы писали:
M>Как это читаемость будет меньше при большем количестве ничего не делающего кода?
Во-первых, количество кода не обязано быть больше. Это утверждение — не аксиома и нуждается в обосновании.
Во-вторых, читаемость зависит не только от количества кода.
M>Еще раз говорю, покажите реальный проект без DI или хотя бы CRUD заготовку.
Любой (почти) проект на гитхабе бери, там DI не будет никакого.
Здравствуйте, Somescout, Вы писали:
S>Это, вобще-то, и есть dependency injection. Только вы это вручную делаете, и фактически переносите проблемы на уровень выше: теперь вызывающий код в обязательном порядке должен иметь ссылку на базу (напрямую или через DatabaseHelper), и сам должен получить userDao — что поменялось в итоге от такой перетасовки? Ничего — все проблемы ровно на том же месте.
Как минимум поменялось то, что теперь это всё делается явно. Не надо гадать кто, где, когда, зачем. Этот код можно перенести в другой проект и он либо будет работать, либо не скомпилируется. А в случае с контейнерами он скорее всего скомпилируется, но работать не будет.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Somescout, Вы писали:
IT>>А раз так, то как же можно получить информацию о кастомере без DI фреймворка и пары IoC контейнеров? Никак не можно. S>Кстати, а если действительно никак не можно — например информация о кастомере разбросана по нескольким сервисам?
И как тут поможет DI фреймворк?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, microuser, Вы писали:
M>>Как это читаемость будет меньше при большем количестве ничего не делающего кода?
ARK>Во-первых, количество кода не обязано быть больше. Это утверждение — не аксиома и нуждается в обосновании. ARK>Во-вторых, читаемость зависит не только от количества кода.
M>>Еще раз говорю, покажите реальный проект без DI или хотя бы CRUD заготовку.
ARK>Любой (почти) проект на гитхабе бери, там DI не будет никакого.
CalculatePriceHandler нигде руками не создается, так же как и его зависимость IDataStore, все достается из контейнера. Теперь ваша очередь, покажите любой CRUD проект без DI.
Классный пример. На первый взгляд код с DI получается короче. Но если внимательно присмотреться, то можно заметить, что код без DI самодостаточен и понятен, а краткость без DI достигается банальным опусканием реализации некоторых объектов Даже в таком мелком примере умудрились всё запутать
Если нам не помогут, то мы тоже никого не пощадим.
Откуда-то взялось дополнительное условие "CRUD-проект".
Ну вот первый попавшийся: https://github.com/dlang/dmd/tree/master/src/dmd
Догадываюсь, что это не "CRUD-проект" (что бы это ни значило), но подозреваю, что сложность его на пару порядков больше, чем типичного "круда". Однако, почему-то напрямую вызываются конструкторы, без всяких инъекций.
Здравствуйте, Sinclair, Вы писали:
S>>Ок, простой пример: на веб странице компонент должен выводить имя текущего пользователя. Он должен его откуда-то получить: либо вы при рендеринге каждой страницы прокидываете эту информацию ему вручную, либо компонент так или иначе (через DI, репозиторий, синглтон) получает эту информацию сам. S>Ну это же прекрасный пример. Через три-четыре релиза у нас в топ жалоб выезжает "ваше приложение нещадно тормозит", привлекается команда высокооплачиваемых экспертов, которые после трёх месяцев расчистки конюшен констатирует очевидное: "у вас там каждая кнопка в гуе считает, что ей нужно лазить в базу/сторонний сервис/ещё куда-то, в итоге для построения одного экрана открывается 27 коннектов к базе, и исполняется более трёхсот SQL-запросов. Из них треть дублируют друг друга, а половина, хоть и не совпадает, может быть покрыта одним из более широких запросов". Рекомендация экспертов: "чётко разделить фазы построения гуя, доставая данные из базы минимальным количеством запросов и передавая визуальным компонентам готовую viewModel".
А DI то тут причем? Не вижу связи с оптимизацией sql запросов, что правильно, и DI. Тоже самое отменно может быть
и без плюшек DI.
Здравствуйте, AlexRK, Вы писали:
ARK>Это все как раз про DI. Особенно читаемость — она там где-то в районе нуля.
Ну тормоза то будут на старте, при создании графов объектов.
ARK>Плюс лишние тормоза и еще потеря статической типизации в точке получения объекта.
Не будет там потери, просто меньше компилятора предупреждений от компилятора переедут в
падение во время исполнения -- при запуске, например. Я выше ссылку на SO привел. Т.е.
если где-то чего-то не хватает, мы узнаем только при запуске.
Здравствуйте, AlexRK, Вы писали:
S>>То, что в недрах гуя нет простого доступа к DBConnection — это хорошо, а не плохо. Так что нам не нужно средство, которое даёт эту невыносимую лёгкость сделать "а шандарахну-ка я тут запросец в базу". ARK>Плюс адин. Провоцирует на повышение связности на ровном месте. В результате на выходе Big Ball of Mud.
Нет, провоцирует сложность конфигурации и т.п. Связность как была низкой -- по интерфейсам -- так и осталось.
Здравствуйте, TG, Вы писали:
TG>>>Или получается, что DI полезен только вот в таких "хитрых" архитектурах? S>>Что "хитрого" в такой архитектуре? TG>Фраза "Допустим мне в глубинах компонентов понадобился доступ к базе" намекает, что при проектировании несколько подзабыли принципы SOLID, разделение на слои и т.д. И если сделано это осознанно, то было бы опять же интересно посмотреть на конкретный кейс.
Скорее про DDD какое-нибудь забыли\отступись, а доступ в глубинах компонентов к бд можно и по SOLID сделать.
Здравствуйте, IT, Вы писали:
S>>Это, вобще-то, и есть dependency injection. Только вы это вручную делаете, и фактически переносите проблемы на уровень выше: теперь вызывающий код в обязательном порядке должен иметь ссылку на базу (напрямую или через DatabaseHelper), и сам должен получить userDao — что поменялось в итоге от такой перетасовки? Ничего — все проблемы ровно на том же месте. IT>Как минимум поменялось то, что теперь это всё делается явно. Не надо гадать кто, где, когда, зачем. Этот код можно перенести в другой проект и он либо будет работать, либо не скомпилируется. А в случае с контейнерами он скорее всего скомпилируется, но работать не будет.
Вроде договорились, что серебряной пули не существует. Программист может абстрагироваться от кучи
реализаций абстракций, их конфигурации и т.п., а просто будет работать с тем, что есть, т.е. дадут\сконфигурируют.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, microuser, Вы писали:
M>>Ну взял первый попавшийся, DI на месте: M>>https://github.com/asc-lab/dotnetcore-microservices-poc/blob/master/PricingService/Commands/CalculatePriceHandler.cs M>>CalculatePriceHandler нигде руками не создается, так же как и его зависимость IDataStore, все достается из контейнера. Теперь ваша очередь, покажите любой CRUD проект без DI.
ARK>Откуда-то взялось дополнительное условие "CRUD-проект". ARK>Ну вот первый попавшийся: https://github.com/dlang/dmd/tree/master/src/dmd ARK>Догадываюсь, что это не "CRUD-проект" (что бы это ни значило), но подозреваю, что сложность его на пару порядков больше, чем типичного "круда". Однако, почему-то напрямую вызываются конструкторы, без всяких инъекций.
Конечно не CRUD и естественно здесь никакой DI не нужен. Просто потому что здесь сложности в создании объектов никакой нет, их тут всего штук 50(судя по количеству файлов) и они не меняются так как бизнес логики нет, а новые будут добавляться лишь изредка. По большому счету компилятор это функция которой на вход подаются исходники, а на выходе исполняемый файл. DI нужен в "энтерпрайз" разработке, где куча разнообразной логики которая имеет свойство часто меняться, это приводит к тому что меняются сигнатуры конструкторов, их зависимости и т.д.
Здравствуйте, Министр Промышленности, Вы писали:
МП>в таком описании становится понятно, что оставление DI в проекте есть ни что иное как технических долг... МП>просто по завершении работ по быстро обновимшимся требованиям забили болт на реформирование структуры классов по сборкам
Скорее гарантия гибкости и производительности программиста -- не надо разбираться с кучей сервисов, их
отличий, особенностей и т.п. , а просто "заказать" себе текущую реализацию (интерфейс) и плясать от нее.
Здравствуйте, TG, Вы писали:
TG>А почему оставим-то? Это очень интересный вопрос. TG>Или получается, что DI полезен только вот в таких "хитрых" архитектурах? TG>А если что-то пилить с нуля, то можно и обойтись без DI?
Наверное, даже нужно. Но если все это разрастется до энтерпрайза с постоянно меняющимися требования,
тогда придется использовать DI.
Здравствуйте, microuser, Вы писали:
M>Не совсем понимаю связь между неявной параметризацией функций, смешиванием бизнес логики и зависимостей от внешнего поведения. M>Все это вопросы архитектуры, а DI фреймворк лишь берет на себя ответственность за создание экземпляров объектов, работать он может в любой архитектуре. M>Хотелось бы real life пример бизнес приложения, или хотя бы CRUD hello world без DI.
Ну наше приложения я, естественно, тебе показывать не могу, а рыться по гитхабу не буду.
Но я тебе могу предложить кое-что лучше — целая статья на эту тему, где все разжевано на пальцах. https://fsharpforfunandprofit.com/posts/dependencies/
Здравствуйте, varenikAA, Вы писали:
AA>Здравствуйте, barn_czn, Вы писали:
_>>Конкретный пример пож-та, чтобы спор был не абстрактным.
AA>И да, еще больше примеров в книжке "Роберт Мартин — Чистая архитектура — 2018", после ее прочтения сомнения отпадают. AA>Так или иначе зависимости будут, использовать для этого ФВ или делать свой велосипед это уже от архитектора зависит. AA>Хотя да, большинство проектов на C# это монолиты.
причем тут c#? а на ява или с++ ни монолиты? Большинство проектов.. учитывая популярность Unity сейчас можно сказать что большинство проектов на шарпе — полный гавнокод от школьников. Шарп тут не причем как ЯП.
AA>В корэ использую стандартный DI очень доволен. можно в любом месте приложения получить ссылку на любой объект не задумываясь.
AA>https://github.com/altbodhi/HostAppExample/blob/master/Program.cs
Все равно ни понял. Контексты всегда были и без DI (Control.Parent, Control.Application, Request.Host и т.д). Добирайся нихочу.
Но вот если есть конструктор у App, а я явно не вижу место его вызова — меня это напрягает. Таких неявных мест должно быть по минимуму.
Здравствуйте, IT, Вы писали:
IT>Я такое при возможности выкорчёвываю нещадно. На самом деле есть очень простой и эффективный критерий правильно написанной бизнес логики. Если логически законченный функционал бизнес логики легко извлекается из одного проекта и спокойно переносится в другой, то это правильно написанная бизнес логика. Если же для переезда в соседний дом нужно перетащить с собой весь подъезд, то это не код, а кусок оверинжениринга.
для переиспользования бизнес-логики нужна интеграция, а не копирование (репо, артефактов, классов...). последствия последнего объяснять, думаю, не надо.
и, бинго, даже интеграция делается через некий интерфейс. в случае необходимости его версионирования вводится политика EOL по каждой версии.
Здравствуйте, IT, Вы писали:
IT>Какая прелесть! А кто мешает один раз написать для базы данных какой-нибудь рапер/хелпер, который будет решать все эти вопросы без DI?
Ок, напишу я его, а прокинуть его как? И я опять же не понимаю: зачем "решать эти вопросы без DI" — как это облегчает работу?
Здравствуйте, IT, Вы писали:
IT>Как минимум поменялось то, что теперь это всё делается явно. Не надо гадать кто, где, когда, зачем.
Я уже который раз не понимаю зачем нужно гадать? DI вполне явно инициализируется. Нет, безусловно могут быть ситуации, когда инициализация сделана через аттрибуты, да ещё и в другой assembly, да ещё и без исходников — но это уже проблемы архитектуры, а не DI.
IT>Этот код можно перенести в другой проект и он либо будет работать, либо не скомпилируется. А в случае с контейнерами он скорее всего скомпилируется, но работать не будет.
А что мешает перенести инициализацию DI и всё тогда тоже будет работать? Не говоря уж о том, что если в прицнипе стоит вопрос о "использовать DI или нет", значит код уже нетривиальный и просто так не переедет.
Здравствуйте, IT, Вы писали:
IT>>>А раз так, то как же можно получить информацию о кастомере без DI фреймворка и пары IoC контейнеров? Никак не можно. S>>Кстати, а если действительно никак не можно — например информация о кастомере разбросана по нескольким сервисам?
IT>И как тут поможет DI фреймворк?
Если у вас зависимость от множества сервисов, то прежде чем воспользоваться GetCustomer вам нужно их все получить. Да, можно обернуть это всё в Helper — но какой смысл плодить сущности без нужды?
Здравствуйте, C0s, Вы писали:
IT>>Я такое при возможности выкорчёвываю нещадно. На самом деле есть очень простой и эффективный критерий правильно написанной бизнес логики. Если логически законченный функционал бизнес логики легко извлекается из одного проекта и спокойно переносится в другой, то это правильно написанная бизнес логика. Если же для переезда в соседний дом нужно перетащить с собой весь подъезд, то это не код, а кусок оверинжениринга.
C0s>для переиспользования бизнес-логики нужна интеграция,
Интеграция чего?
C0s>а не копирование (репо, артефактов, классов...). последствия последнего объяснять, думаю, не надо.
Никто и не говорил о копировании. Речь шла о переносе кода из одного проекта в другой.
C0s>и, бинго, даже интеграция делается через некий интерфейс. в случае необходимости его версионирования вводится политика EOL по каждой версии.
Вот вроде все слова понятные, а смысла не улавливаю. EOL — это end of line?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>>>Я такое при возможности выкорчёвываю нещадно. На самом деле есть очень простой и эффективный критерий правильно написанной бизнес логики. Если логически законченный функционал бизнес логики легко извлекается из одного проекта и спокойно переносится в другой, то это правильно написанная бизнес логика. Если же для переезда в соседний дом нужно перетащить с собой весь подъезд, то это не код, а кусок оверинжениринга.
C0s>>для переиспользования бизнес-логики нужна интеграция,
IT>Интеграция чего?
в данном случае важно — с чем, вот с ней, этой самой БЛ
C0s>>а не копирование (репо, артефактов, классов...). последствия последнего объяснять, думаю, не надо.
IT>Никто и не говорил о копировании. Речь шла о переносе кода из одного проекта в другой.
ага, перенос — не копирование?
C0s>>и, бинго, даже интеграция делается через некий интерфейс. в случае необходимости его версионирования вводится политика EOL по каждой версии.
IT>Вот вроде все слова понятные, а смысла не улавливаю. EOL — это end of line?
Здравствуйте, Somescout, Вы писали:
S>А что мешает перенести инициализацию DI и всё тогда тоже будет работать? Не говоря уж о том, что если в прицнипе стоит вопрос о "использовать DI или нет", значит код уже нетривиальный и просто так не переедет.
Давай мы сразу определимся. Речь идёт не о DI как таковом, а о DI фреймворках и всяческих IoC контейнерах. Ты сейчас про что?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Somescout, Вы писали:
IT>>И как тут поможет DI фреймворк? S>Если у вас зависимость от множества сервисов, то прежде чем воспользоваться GetCustomer вам нужно их все получить. Да, можно обернуть это всё в Helper — но какой смысл плодить сущности без нужды?
У меня нет множества сервисов, только база данных. Как тут поможет DI фреймворк?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, C0s, Вы писали:
C0s>>>для переиспользования бизнес-логики нужна интеграция, IT>>Интеграция чего? C0s>в данном случае важно — с чем, вот с ней, этой самой БЛ
Возможно. Но без "чего" я не улавливаю смысла в твоём сообщении. А с телепатией у меня всегда были проблемы.
IT>>Никто и не говорил о копировании. Речь шла о переносе кода из одного проекта в другой. C0s>ага, перенос — не копирование?
Не, не копирование. Впрочем, если ты о копипасте, то тут тоже не всё так однозначно.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
C0s>>>>для переиспользования бизнес-логики нужна интеграция, IT>>>Интеграция чего? C0s>>в данном случае важно — с чем, вот с ней, этой самой БЛ
IT>Возможно. Но без "чего" я не улавливаю смысла в твоём сообщении. А с телепатией у меня всегда были проблемы.
"чего" — это "что-то", что должно от реализации этой БЛ зависеть.
IT>>>Никто и не говорил о копировании. Речь шла о переносе кода из одного проекта в другой. C0s>>ага, перенос — не копирование?
IT>Не, не копирование. Впрочем, если ты о копипасте, то тут тоже не всё так однозначно.
не всё, но я лишь говорю, что зависимость в таком случае должна достигаться по векторам деплоймента, а не путём копирования кода реализации БЛ, что мне показалось подразумеваемым твоим комментарием, на который я начал отвечать. допускаю, что у меня тоже телепатия просела.
S>>А вообще, в целом, я согласен с теми из участников дискуссии, которые считают развитые DI-контейнеры всего лишь средством переноса сложности из исходного кода в конфигурацию. S>У меня складывается впечатление что некоторые участники дискуссии слегка побаиваются DI.
потому что, мать его, столкнулись с последствиями его внедрения
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
при всём уважении у меня не повернётся язык назвать такой retriever-метод бизнес-логикой
по крайней мере, тяжело представить такой метод изменяющимся часто на протяжении жизни проекта
C0s>>не всё, но я лишь говорю, что зависимость в таком случае должна достигаться по векторам деплоймента,
IT>Зависимости не должны достигаться. Они должны устраняться?
а как их устранить-то, если есть базовая зависимость по бизнес-требованиям?
в случае, когда БЛ подлежит эволюции, это будет означать, что конкретный алгоритм, берущий на вход A и порождающий B, может менять типы A и B, как формально, так и
семантически (при совпадении формальных типов ингредиентов, если так почему-то неудачно заложили сразу), а клиенты этого алгоритма должны подстраиваться под его эволюцию
при таком раскладе между копированием кода или же зависимостью по артефакту, что означает неконтролируемость по EOL, я рекомендую зависимость по деплойменту, которая отключается на стороне заинтересованного "сервера", а не путём поиска по дереву вниз всех нерадивых, кто не успел перейти на следующий вариант
ps. мы всё дальше отходим от DI, как фичи какого-то фреймворка
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, Somescout, Вы писали:
IT>>>Какая прелесть! А кто мешает один раз написать для базы данных какой-нибудь рапер/хелпер, который будет решать все эти вопросы без DI? S>>Ок, напишу я его, а прокинуть его как? И я опять же не понимаю: зачем "решать эти вопросы без DI" — как это облегчает работу?
IT>Ты не правильно ставишь вопрос. Т.к. это ты добавляешь в проект новый инструмент, то вопрос должен ставится как "какую проблему этот инструмент решает, какие негативные последсвия вносит и каково их соотношение". Т.е. польза, вред, что больше.
Стоп. Давайте вы сначала покажете правильный (с вашей точки зрения) способ использования этого хелпера, а потом порассуждаем о достоинствах и недостатках. Иначе я буду просто предполагать, что вы предлагаете статический синглтон — и, думаю, не нужно говорить почему это плохая практика.
S>>>>А вообще, в целом, я согласен с теми из участников дискуссии, которые считают развитые DI-контейнеры всего лишь средством переноса сложности из исходного кода в конфигурацию. S>>>У меня складывается впечатление что некоторые участники дискуссии слегка побаиваются DI.
МП>>потому что, мать его, столкнулись с последствиями его внедрения
S>То есть детский испуг? Ну накнулись на странную и непонятную штуку и "ААААА!!!!" (и под одеяло).
нет
просто реально трудно и неприятно работать когда это нагорожено
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, barn_czn, Вы писали:
_>причем тут c#? а на ява или с++ ни монолиты? Большинство проектов.. учитывая популярность Unity сейчас можно сказать что большинство проектов на шарпе — полный гавнокод от школьников. Шарп тут не причем как ЯП.
Большинство ЯП монолиты, из модульных только оберон знаю ))
ЯП хороший, но требует огромного опыта для написание простого и надежного кода. Поэтому думаю начинать изучение программирования лучше с лиспа или F#.
_>Но вот если есть конструктор у App, а я явно не вижу место его вызова — меня это напрягает. Таких неявных мест должно быть по минимуму.
Вчера как раз в чате F# осбуждали, тоже считают это не очень хорошая концепция
Здравствуйте, barn_czn, Вы писали:
_>Все равно ни понял. Контексты всегда были и без DI (Control.Parent, Control.Application, Request.Host и т.д). Добирайся нихочу. _>Но вот если есть конструктор у App, а я явно не вижу место его вызова — меня это напрягает. Таких неявных мест должно быть по минимуму.
Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов.
Взять теже фабрики, билдеры они тоже зло?
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, microuser, Вы писали:
M>>Хотелось бы real life пример бизнес приложения, или хотя бы CRUD hello world без DI.
IT>Давай ты нам приведёшь такой пример с DI, а мы его переделаем без DI.
Ну так привели же уже. Вон, прямо в википедии чёрным по белому написано "смотрите, какой ужас получается при Внедрении зависимости при помощи фреймворка".
Берём, выкидываем нафиг говноXML, возвращаемся на шаг "без использования dependency injection" и получаем нормальный императивный код.
В котором внесение изменений в конструктор двигателя подскажет нам, что поменять в конструкторе автомобиля. И прямо в IDE, даже до запуска компилятора.
Если вдруг нам потребуется конструировать автомобили с разными типами двигателей (а нам потребуется? Ну, вот по правде?), то мы перейдём к "внедрению зависимости вручную" — и у нас все зависимости продолжат оставаться под контролем компилятора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
SD>PS: написанное выше относится только к слову "конфигурация". Но не с dependency injection. Эта идиома по сути своей представляет не что иное как способ задавать интерфейс (API). Почему придумали новый термин... ну, сразу вот это вспоминается:
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, AlexRK, Вы писали:
ARK>>Это меня на текущем проекте просто достало. Чуваки с золотыми молотками суют это уродство во все щели — и куда надо, и куда не надо (на самом деле почти никуда не надо). Гора интерфейсов, за которыми найти не возможно просто ничего. Тьфу.
IT>Я такое при возможности выкорчёвываю нещадно. На самом деле есть очень простой и эффективный критерий правильно написанной бизнес логики. Если логически законченный функционал бизнес логики легко извлекается из одного проекта и спокойно переносится в другой, то это правильно написанная бизнес логика. Если же для переезда в соседний дом нужно перетащить с собой весь подъезд, то это не код, а кусок оверинжениринга.
Обычно функционал бизнес-логики сильно приколочен гвоздями к структуре данных (т.е. подъезду). Это и мешает переносу.
А если начнем "абстрагироваться" от конкретной структуры данных вводя промежуточные абстракции-структуры, то в пределе снова приходим к оверинжинирингу и упомянутой вами ранее проблеме
Проблема и непонимание заключается в том, что бизнес логика, которая будет многократно менятся, пишется как универсальный код, предназначенный для многократного повторного использования
IT>Ты не правильно ставишь вопрос. Т.к. это ты добавляешь в проект новый инструмент, то вопрос должен ставится как "какую проблему этот инструмент решает, какие негативные последсвия вносит и каково их соотношение". Т.е. польза, вред, что больше.
А если, например, так? Если максимально "близко к телу". Представим, что определение всей конфигурации IoC-контейнера происходит в одном файле, кодом (не через xml, аннотации и прочую муть, просто в каком-то классе с конфигурацией модулей).
— определение реализации для интерфейсов или просто регистрация классов (самих по себе) в контейнере.
— конфигурирование времени жизни объектов классов/реализаций классов (правил инстанциации — синглтон, сессионный, по одному на каждую зависимость и т.д.), зарегистрированных в контейнере.
— автоматическое удовлетворение зависимостей между всеми классами, в соответствии с определенными выше правилами по времени жизни/созданию при автоматическом конструировании объекта из контейнера.
Таким образом:
— вся конфигурация наглядна и все правила описаны кодом в одном файле. Это, конечно, может не так явно, как "Class class = new Class", но ведь у этого "явного" подхода тоже есть минусы (отсутствие "единого языка", т.е. api который даёт готовый ioc-контейнер). Подход с конфигурированием IoC-контейнера в одном файле может быть непонятен скорее из-за того, что он не так традиционен, конечно.
— по мере роста приложения все новые компоненты (т.е. классы) продолжают регистрироваться и описываться в одном месте, в этом классе-конфиге, наглядно (к этому принуждает в т.ч. архитектура приложения, работающая с контейнером для создания объектов).
— при создании компонента (класса) с зависимостями, зарегистрированными в ioc-контейнере, все эти зависимости отслеживаются и создаются автоматически согласно конфигурации ioc-контейнером (кажется, начинаю повторяться )
— конфигурацию можно иметь отдельную (или на основе первой) для тестов, подсовывая при регистрации для интерфейсов другие реализации, ну и прочее в таком роде.
Понятно, что не все классы обязаны инстациироваться таким образом через сконфигурированный контейнер, конечно.
Т.е. мы (ценой добавления библиотеки, да) получаем в основном инструмент для а) более удобного (чем описание этого всего "своим языком" явно) конфигурирования интерфейсов/реализаций; б) следующего из предыдущего автоматического создания объектов по этим правилам ioc-контейнером, с соблюдением зависимостей между ними.
Это ведь вроде, так или иначе, придётся делать самому. Можно делегировать ioc-контейнеру. Интересно ваше мнение.
МП>я имел в виду банальную задачу: МП>есть фарш исторического кода, который неправильно работает МП>ты разбираешься в какой момент всё идёт неправильно, отслеживаешь объекты где используются, откуда получаются МП>и вот на 7м-13м уровне вложенности вызывов ты понимаешь, тебе надо срочно знать откуда берётся значение вот в этом объекте МП>прыг F12 (у меня это решарперовский GoToDefinition) МП>ага есть конструктор прыг Alt+F7 (у меня это FindUsages) а хрен тебе — он вызывается неявно в рамках DI МП>и хорошо если ты это ещё знаешь МП>а то вполне можешь подумать "ага, значит инициализируется не здесь", и пойти исследовать фарш дальше вглубь! МП>и это убийственно
ОК. Но при чём тут автоматический рефакторинг?
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
И почему МП>и это не перекрывается гибкостью подстановки mock-объектов
?
Здравствуйте, C0s, Вы писали:
IT>>Что здесь куда интегрировать?
C0s>при всём уважении у меня не повернётся язык назвать такой retriever-метод бизнес-логикой C0s>по крайней мере, тяжело представить такой метод изменяющимся часто на протяжении жизни проекта
Это мне понятно. Когда в руках молоток, то всё кажется гвоздями. А раз так, то для реализации такой простой логики нужно ещё восемь интерфейсов, развесистую иерархию классов и десяток DTO объектов. Тут уж понятное дело без DI никуда.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Somescout, Вы писали:
S>>>Ок, напишу я его, а прокинуть его как? И я опять же не понимаю: зачем "решать эти вопросы без DI" — как это облегчает работу? IT>>Ты не правильно ставишь вопрос. Т.к. это ты добавляешь в проект новый инструмент, то вопрос должен ставится как "какую проблему этот инструмент решает, какие негативные последсвия вносит и каково их соотношение". Т.е. польза, вред, что больше. S>Стоп. Давайте вы сначала покажете правильный (с вашей точки зрения) способ использования этого хелпера, а потом порассуждаем о достоинствах и недостатках. Иначе я буду просто предполагать, что вы предлагаете статический синглтон — и, думаю, не нужно говорить почему это плохая практика.
Не-не-не. Нам твои DI фреймворки нафиг не нужны и нам без них хорошо. Покажи, что мы не правы, приведи код, где DI фремворки рулят.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Somescout, Вы писали:
IT>>Давай мы сразу определимся. Речь идёт не о DI как таковом, а о DI фреймворках и всяческих IoC контейнерах. Ты сейчас про что? S>О них в том числе. Я просто действительно не понимаю что именно вас в них смущает: у меня два проекта, когда из первого, построенного на DI потребовалось перенести компоненты во второй (без DI) — не возникло никаких проблем, я просто руками конструировал все объекты.
Т.е. чтобы перенести код пришлось его не слабо так переписать. Правильно?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Somescout, Вы писали:
S>Да я вроде уже в первом же комментарии это объяснял — вам не нужно конструировать контекст базы непосредственно внизу. А если абстрагироваться через интерфейсы, то компонент может вообще не знать с чем именно он работает — есть интерфейс для получения данных, он его и использует. А что за ним прячется: сама база, кэш или сервис — его не касается.
При желании можно абстрагироваться даже от интерфейсов. Но я не буду говорить как, не дай боже вам это понравится.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Sinclair, Вы писали:
S>Ну так привели же уже. Вон, прямо в википедии чёрным по белому написано "смотрите, какой ужас получается при Внедрении зависимости при помощи фреймворка".
Я уже по этому поводу отписался. Они даже на простейшем примере умудрились всё запутать.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, MadHuman, Вы писали:
MH>Обычно функционал бизнес-логики сильно приколочен гвоздями к структуре данных (т.е. подъезду). Это и мешает переносу.
Правильно. Поэтому данные нужно тоже чётко различать. Есть данные общие для всего приложения. Как правило они не так многочисленны и их структура более стабильна. Их можно оформить в виде отдельного компонента. Структуры данных, специфичные для конкретной бизнес логики, могут (и должны) находиться где-нибудь поблизости или, как минимум, обособленно.
MH>А если начнем "абстрагироваться" от конкретной структуры данных вводя промежуточные абстракции-структуры, то в пределе снова приходим к оверинжинирингу и упомянутой вами ранее проблеме
А вот этого не надо.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, microuser, Вы писали:
M>Например есть у нас некий полезный класс у которого две зависимости:
M>class Service
M>{
M> public Service(IDependency1 dependency1, IDependency2 dependency2)
M> {
M> }
M> public void DoSomeWork()
M> {
M> //...
M> }
M>}
Можно я ещё немного добавлю полезности? Спасибо!
enum SourceType
{
Foo,
ar
}
class Service
{
public Service(IDependency1 dependency1, IDependency2 dependency2, SourceType sourceType)
{
}
public void DoSomeWork()
{
//...
}
}
Как DI фреймворк разрулит ситуацию для разных sourceType?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, SkyDance, Вы писали:
SD>Потому что конфигурирование и есть кодирование. SD>Более того, "конфигурирование" требует ровно того же тестирования, что и "кодирование", и ровно такого же процесса выкатывания изменений.
Можно пример тестирования конфигурации Production?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, microuser, Вы писали:
M>С DI фреймворком ровным счетом ничего не меняется, только Shift-F12 нажимается не на конструкторе, а на названии класса. Дальше надо посмотреть лишь классы у которых в конструкторе есть параметр с типом Foo, и так же по цепочке раскручивается. Проблема надуманная, как мне кажется.
И что это решает? Вот нашёл ты класс, который принимает Foo как параметр. Но преждем чем его принять тебе нужно создать этот Foo, а он при создании падает.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, microuser, Вы писали:
M>>С DI фреймворком ровным счетом ничего не меняется, только Shift-F12 нажимается не на конструкторе, а на названии класса. Дальше надо посмотреть лишь классы у которых в конструкторе есть параметр с типом Foo, и так же по цепочке раскручивается. Проблема надуманная, как мне кажется.
IT>И что это решает? Вот нашёл ты класс, который принимает Foo как параметр. Но преждем чем его принять тебе нужно создать этот Foo, а он при создании падает.
Так в данном примере вообще ничего искать не нужно, т.к. при использовании DI экземпляр Bar будет создаваться контейнером и все его зависимости будут так же создаваться контейнером, соответственно нужно посмотреть только код конфигурации этого контейнера. Кроме того современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось.
Здравствуйте, IT, Вы писали:
IT>Можно я ещё немного добавлю полезности? Спасибо!
IT>
IT>enum SourceType
IT>{
IT> Foo,
IT> ar
IT>}
IT>class Service
IT>{
IT> public Service(IDependency1 dependency1, IDependency2 dependency2, SourceType sourceType)
IT> {
IT> }
IT> public void DoSomeWork()
IT> {
IT> //...
IT> }
IT>}
IT>
IT>Как DI фреймворк разрулит ситуацию для разных sourceType?
Тут создается зависимость на SourceType.
Суть DI паттерна как раз писать код так что бы не было зависимостей:
в конструкторе/методе мы объявляем контракты объектов, который можно передать как параметры, и нам не важно как реализованы эти объекты и что они будут делать.
Потому DI паттерн от фреймверков не зависит, фреймверки вообще можно не использовать для внедрения зависимостей.
Напр. тут мы внедряем зависимости без фреймверка, а просто вручную создаем все нужные нам объекты:
class Service1 : IService1
{
public Service(IDependency1 dependency1, IDependency2 dependency2) { }
}
class Service2
{
public Service(IService1) { }
}
void Main(){
var dependency1 = new Dependency1();
var dependency2 = new Dependency2();
var service1 = new Service1(dependency1,dependency2);
var service2 = new Service2(service1);
}
Підтримати Україну у боротьбі з країною-терористом.
Здравствуйте, petroa, Вы писали:
IT>>Код кидает исключение. Твоя задача — найти причину. Если код без DI фрейворков, то эта задача из разряда примитивных.
P>Но в описываемой мною ситуации b не может быть null по определению,
Вот почему я даже не сомневался, что абстрагироваться от конкретной проблемы апологетам DI будет крайне трудно?
Давай по-другому. Bar приезжает, но поломаный и проблема возникает только в Foo. Нужно быстро найти того, кто мог сломать Bar перед тем как передать его в Foo.
P>Я хотел примерно передать то, что выше описал (наверное, сумбурно). Я исходил из допущения, что разработчик а) знает о том, как работает ioc-контейнер в смысле того, что я выше описал; б) знает о конфигурировании сервисов в рамках приложения через ioc-контейнер. Но это же вроде классические для ioc-контейнеров понятия, т.е. ситуации: IT>>Если же используется DI фреймворк, то для того, чтобы понять что происходит необходимо запускать как минимум отладчик. P>Возникнуть вроде и не должно.
Причём здесь знает или не знает? В концепции DI нет ничего сложного. В том или ином виде это используется уже десятки лет. Проблема в неявности. Это примерно как динамическая типизация против статической. При статической накосячить не даст компилятор. Проблемы динамической зачастую без отладчика не решаются.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, microuser, Вы писали:
M>Так в данном примере вообще ничего искать не нужно, т.к. при использовании DI экземпляр Bar будет создаваться контейнером и все его зависимости будут так же создаваться контейнером, соответственно нужно посмотреть только код конфигурации этого контейнера.
И что я там увижу? Список имён классов?
M>Кроме того современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось.
По-поводу умения абстрагироваться я уже выше отписал.
Если нам не помогут, то мы тоже никого не пощадим.
IT>Вот почему я даже не сомневался, что абстрагироваться от конкретной проблемы апологетам DI будет крайне трудно? IT>Давай по-другому. Bar приезжает, но поломаный и проблема возникает только в Foo. Нужно быстро найти того, кто мог сломать Bar перед тем как передать его в Foo.
Но каким образом он приезжает "поломанный", если он только что создан по правилам, определенным разработчиком же в конфигурации контейнера? Если его состояние не удовлетворяет получателя, то тоже совершенно всё очевидно где искать.
IT>Причём здесь знает или не знает? В концепции DI нет ничего сложного. В том или ином виде это используется уже десятки лет. Проблема в неявности. Это примерно как динамическая типизация против статической. При статической накосячить не даст компилятор. Проблемы динамической зачастую без отладчика не решаются.
Я как раз и пишу, что нет никакой неявности. Все совершенно явно описывается в конфигурации. Точно так же оно бы описывалось и руками, без контейнера ведь.
И просьба, все же, не надо про "я даже не сомневался, что апологетам DI будет крайне трудно", я в них не записывался
Здравствуйте, petroa, Вы писали:
P>Но каким образом он приезжает "поломанный", если он только что создан по правилам, определенным разработчиком же в конфигурации контейнера?
Мы тут выше обсуждали сценарий с Data Connection, которое живее всех живых в рамках сессии. Давай сделаем такое же допущение для Bar.
P>Я как раз и пишу, что нет никакой неявности. Все совершенно явно описывается в конфигурации. Точно так же оно бы описывалось и руками, без контейнера ведь.
Явно — это когда проверяется компилятором.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Мы тут выше обсуждали сценарий с Data Connection, которое живее всех живых в рамках сессии. Давай сделаем такое же допущение для Bar.
Не читал особо, потому уточню. Bar — синглтон в рамках приложения, и ломается кем-то где-то, ты об этом? И в другом месте приезжает "поломанный"? Ну блин, как же это соотносится с ioc-контейнерм vs. условное ручное создание объектов. Хотя может неправильно понял, но честно, въезжать в какие-то контексты споров о сценариях с "Data Connection, которое живее всех живых в рамках сессии"... мы же в нашем контексте спорим.
IT>Явно — это когда проверяется компилятором.
Это идет вразрез с примерами, которые приводились, вроде "приезжает поломанным", или этот:
IT>
IT>class Foo
IT>{
IT> public Foo(Bar b)
IT> {
IT> if (b == null)
IT> throw ...
IT> }
IT>}
IT>
Честно, я не очень понял почему у нас вдруг всплыл компилятор Как-то сильно в сторону, в другую оперу.
Здравствуйте, IT, Вы писали:
S>>Стоп. Давайте вы сначала покажете правильный (с вашей точки зрения) способ использования этого хелпера, а потом порассуждаем о достоинствах и недостатках. Иначе я буду просто предполагать, что вы предлагаете статический синглтон — и, думаю, не нужно говорить почему это плохая практика.
IT>Не-не-не. Нам твои DI фреймворки нафиг не нужны и нам без них хорошо. Покажи, что мы не правы, приведи код, где DI фремворки рулят.
Ок, по сравнению со статическим синглтоном, в DI нет завязки на глобальные объекты и глобальные состояния, управляемое время жизни у всех созданных объектов (например в рамках одного запроса будет использован ровно один контекст базы данных (если так сконфигурировано), кроме того все такие объекты будут освобождены после завершения запроса), предсказуемое время инициализации (синглтон будет инициализирован при первом обращении вызывая сайд-эффекты), можно сделать нормальную абстракцию, не привязываясь (опять же в отличии от глобальных состояний) к конкретным классам, а значит портируемость кода будет существенно выше.
Здравствуйте, IT, Вы писали:
S>>Да я вроде уже в первом же комментарии это объяснял — вам не нужно конструировать контекст базы непосредственно внизу. А если абстрагироваться через интерфейсы, то компонент может вообще не знать с чем именно он работает — есть интерфейс для получения данных, он его и использует. А что за ним прячется: сама база, кэш или сервис — его не касается.
IT>При желании можно абстрагироваться даже от интерфейсов. Но я не буду говорить как, не дай боже вам это понравится.
Само собой не будете: вы уже который комментарий стесняетесь не то что код приводить, но даже хоть чуть-чуть в конкретику углубляться.
S> Нет, не правильно: код остался полностью без изменений. Только вместо вызова вида: S>
var instance = serviceProvider.Get<SomeObject>()
Лукавство. Ты покажи _весь_ код, откуда ты взял serviceProvider?
S> Появилось S>
S> var db = new DbContext();
S> var config = new AppConfig();
S> var instance = new SomeObject(db, config);
S>
А вот это _весь_ код. Помещай его в main и запускай, будет работать. Притом быстро, без всяких рефлексий, рантайм-ошибок и километровых стек-трейсов.
S> Вот и всё различие. Внутренности SomeObject не менялись.
Не понял, а зачем их менять?
МП>>я имел в виду банальную задачу: МП>>есть фарш исторического кода, который неправильно работает МП>>ты разбираешься в какой момент всё идёт неправильно, отслеживаешь объекты где используются, откуда получаются МП>>и вот на 7м-13м уровне вложенности вызывов ты понимаешь, тебе надо срочно знать откуда берётся значение вот в этом объекте МП>>прыг F12 (у меня это решарперовский GoToDefinition) МП>>ага есть конструктор прыг Alt+F7 (у меня это FindUsages) а хрен тебе — он вызывается неявно в рамках DI МП>>и хорошо если ты это ещё знаешь МП>>а то вполне можешь подумать "ага, значит инициализируется не здесь", и пойти исследовать фарш дальше вглубь! МП>>и это убийственно
Y>ОК. Но при чём тут автоматический рефакторинг?
например, ввиду вышеизложенного, ты не можешь зачистить члены класса, которые вроде бы не используются
потому что на самом деле они могут использоваться через DI-фреймворк
и таким образом начинаешь стрематься на каждом шагу, который иначе бы сделал "в потоке"
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Somescout, Вы писали:
S>> Нет, не правильно: код остался полностью без изменений. Только вместо вызова вида: S>>
var instance = serviceProvider.Get<SomeObject>()
·>Лукавство. Ты покажи _весь_ код, откуда ты взял serviceProvider?
В смысле? Взял из конструктора:
public class CallerObj {
private readonly IServiceProvider serviceProvider;
public CallerObj(IServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
public void Something() {
var instance = serviceProvider.Get<SomeObject>();
}
}
Если вам интересен вариант когда провайдер создаётся вручную, то примерно так (псевдокод):
public void Something() {
var serviceBuilder = new ServiceBuilder();
serviceBuilder
.Bind<IDbContext,DbContext>(Scope.Request) // Просто для примера - понято что области запроса в данном случае нет
.Bind<IAppConfig, AppConfig>(Scope.Singleton);
serviceBuilder.Bind<SomeObject>(Scope.Transient); // Тут зависит от реализации DI: NInject умеет создавать произвольный объект,
// если для него зарегистрированы все зависимости, MS DI (из NetCore)
// требует явной регистрации всех создаваемых объектовvar serviceProvider = serviceBuilder.Build();
var instance = serviceProvider.Get<SomeObject>();
}
Понятно что этот способ (т.е. через ServiceProvider) используется исключительно в том случае, когда инициализация SomeObject дорогая и сам объект используется он не всегда (я использую его только в контроллере, когда лишь часть экшенов использует его), в обычном случае будет:
public class CallerObj {
private readonly SomeObject someObject;
public CallerObj(SomeObject someObject) {
this.someObject = someObject;
}
public void Something() {
...
}
}
S>> Появилось S>>
S>> var db = new DbContext();
S>> var config = new AppConfig();
S>> var instance = new SomeObject(db, config);
S>>
·>А вот это _весь_ код. Помещай его в main и запускай, будет работать. Притом быстро, без всяких рефлексий, рантайм-ошибок и километровых стек-трейсов.
Ну да, сравниете с ручным созданием ServiceProvider — разница в несколько строк. И да, у меня почему-то ни разу не возникало ситуации, когда проблемы с DI были с "рантайм-ошибок и километровых стек-трейсов" — обычно просто сообщение, что требуемый объект не зарегистрирован.
И да, этот "весь код" явно ссылается на **реализации** используемых классов, затрудняя его переиспользование (за которое тут так ратуют), более того, если инициализация чуть сложнее (например нужно получить ConnectionString) — этот код ляжет не один раз в настройку DI, а во все его вызовы. Про всякие мелочи, вроде использование пулов или скоупы даже говорить бессмысленно.
S>> Вот и всё различие. Внутренности SomeObject не менялись. ·>Не понял, а зачем их менять?
Это вы у IT спросите. Я с самого начала писал что там ничего не менялось.
Здравствуйте, Somescout, Вы писали:
S> В смысле? Взял из конструктора:
Этот антипаттерн называется Service Locator... Противоположность DI.
S> Если вам интересен вариант когда провайдер создаётся вручную, то примерно так (псевдокод):
Т.е. кода таки больше получилось.
S> Понятно что этот способ (т.е. через ServiceProvider) используется исключительно в том случае, когда инициализация SomeObject дорогая и сам объект используется он не всегда (я использую его только в контроллере, когда лишь часть экшенов использует его),
Вроде это банальный Lazy...
S> ·>А вот это _весь_ код. Помещай его в main и запускай, будет работать. Притом быстро, без всяких рефлексий, рантайм-ошибок и километровых стек-трейсов. S> Ну да, сравниете с ручным созданием ServiceProvider — разница в несколько строк. И да, у меня почему-то ни разу не возникало ситуации, когда проблемы с DI были с "рантайм-ошибок и километровых стек-трейсов" — обычно просто сообщение, что требуемый объект не зарегистрирован.
А если не использовать фреймворк, то такого рода ошибки будут сразу в IDE подсвечиваться, как ошибки компиляции.
S> И да, этот "весь код" явно ссылается на **реализации** используемых классов, затрудняя его переиспользование (за которое тут так ратуют),
Такой код собирает конечное приложение, так называемый Composition Root. Если части приложения нужно переиспользовать, классы можно интегрировать в модуль с подходящими областями видимости и зависимостями, а не кидать всё в глобальный мусорный контейнер.
S> более того, если инициализация чуть сложнее (например нужно получить ConnectionString) — этот код ляжет не один раз в настройку DI, а во все его вызовы. Про всякие мелочи, вроде использование пулов или скоупы даже говорить бессмысленно.
Нет, тот же ConnectionString прокинется через ровно такой же DI.
Здравствуйте, ·, Вы писали:
S>> В смысле? Взял из конструктора: ·>Этот антипаттерн называется Service Locator... Противоположность DI.
Я написал ниже в каких случаях я его использую. От того что что-то называют "антипаттерном" оно не становится автоматически плохим.
S>> Если вам интересен вариант когда провайдер создаётся вручную, то примерно так (псевдокод): ·>Т.е. кода таки больше получилось.
Инициализация DI делается один раз. "Так что кода всё-таки меньше получилось" (и да, "аргумент секретарши" судя по всему идёт через года).
S>> Понятно что этот способ (т.е. через ServiceProvider) используется исключительно в том случае, когда инициализация SomeObject дорогая и сам объект используется он не всегда (я использую его только в контроллере, когда лишь часть экшенов использует его), ·>Вроде это банальный Lazy...
И?
S>> Ну да, сравниете с ручным созданием ServiceProvider — разница в несколько строк. И да, у меня почему-то ни разу не возникало ситуации, когда проблемы с DI были с "рантайм-ошибок и километровых стек-трейсов" — обычно просто сообщение, что требуемый объект не зарегистрирован. ·>А если не использовать фреймворк, то такого рода ошибки будут сразу в IDE подсвечиваться, как ошибки компиляции.
Только в том случае, если вы прямо или косвенно завязаны на конкретные классы. Что, внезапно, тоже "антипаттерн".
S>> И да, этот "весь код" явно ссылается на **реализации** используемых классов, затрудняя его переиспользование (за которое тут так ратуют), ·>Такой код собирает конечное приложение, так называемый Composition Root. Если части приложения нужно переиспользовать, классы можно интегрировать в модуль с подходящими областями видимости и зависимостями, а не кидать всё в глобальный мусорный контейнер.
Когда я вижу как кто-то ударяется в демагогию, у меня возникает стойкое ощущение отсутсвия у него аргументов.
S>> более того, если инициализация чуть сложнее (например нужно получить ConnectionString) — этот код ляжет не один раз в настройку DI, а во все его вызовы. Про всякие мелочи, вроде использование пулов или скоупы даже говорить бессмысленно. ·>Нет, тот же ConnectionString прокинется через ровно такой же DI.
Зачем? Вы просто зададите в DI логику получения зависимости и всё, больше её нигде повторять не нужно.
Здравствуйте, Somescout, Вы писали:
S> ·>Этот антипаттерн называется Service Locator... Противоположность DI. S> Я написал ниже в каких случаях я его использую. От того что что-то называют "антипаттерном" оно не становится автоматически плохим.
И в этих случаях он таки не нужен, внезапно. Т.е. антипаттерн. Вот смотришь на такое CallerObj(IServiceProvider serviceProvider) и пока не просмотришь весь код такого класса — никак не не поймёшь от чего собственно он зависит. Такой класс зависит от _всего_. Глобальные Переменные™, просто под другим соусом.
S> ·>Т.е. кода таки больше получилось. S> Инициализация DI делается один раз. "Так что кода всё-таки меньше получилось" (и да, "аргумент секретарши" судя по всему идёт через года).
Composition Root тоже делается один раз.
S> S>> Понятно что этот способ (т.е. через ServiceProvider) используется исключительно в том случае, когда инициализация SomeObject дорогая и сам объект используется он не всегда (я использую его только в контроллере, когда лишь часть экшенов использует его), S> ·>Вроде это банальный Lazy... S> И?
И то, что достаточно иметь Lazy<SomeObject> из которого можно получить ровно то что надо, а не что угодно и хз что подразумевалось.
S> ·>А если не использовать фреймворк, то такого рода ошибки будут сразу в IDE подсвечиваться, как ошибки компиляции. S> Только в том случае, если вы прямо или косвенно завязаны на конкретные классы. Что, внезапно, тоже "антипаттерн".
Ты так говоришь как будто ...Bind<IDbContext,DbContext>... не завязано на конкретные классы. Суть Composition Root это и есть связывание конкретных классов.
S> S>> И да, этот "весь код" явно ссылается на **реализации** используемых классов, затрудняя его переиспользование (за которое тут так ратуют), S> ·>Такой код собирает конечное приложение, так называемый Composition Root. Если части приложения нужно переиспользовать, классы можно интегрировать в модуль с подходящими областями видимости и зависимостями, а не кидать всё в глобальный мусорный контейнер. S> Когда я вижу как кто-то ударяется в демагогию, у меня возникает стойкое ощущение отсутсвия у него аргументов.
В чём демагогия? Контейнер это глобальная свалка всех объектов приложения, из которой беcконтрольно тянется всё отовсюду.
S> ·>Нет, тот же ConnectionString прокинется через ровно такой же DI. S> Зачем? Вы просто зададите в DI логику получения зависимости и всё, больше её нигде повторять не нужно.
Да, не нужно. Что ты имеешь в виду может повторяться и зачем?
IT>- этот код может находится в трудно доступном месте и для его выполнения потребуется создание определённого сценария, подготовки окружения и набора данных. IT>- проблема может возникать только в определённом окружении недоступном для отладки, а при создании искуственных условий проблема не воспроизводится.
кстати да
и это дефакто означает всю ту же связность компонентов в таких случаях!
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
IT>>Т.е. чтобы перенести код пришлось его не слабо так переписать. Правильно?
S>Нет, не правильно: код остался полностью без изменений. Только вместо вызова вида: S>
var instance = serviceProvider.Get<SomeObject>()
S>Появилось S>
S>var db = new DbContext();
S>var config = new AppConfig();
S>var instance = new SomeObject(db, config);
S>
я такое пишу, если умещается в экран, в одну строчку:
var instance = new SomeObject(new DbContext(), new AppConfig());
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
IT>Можно пример тестирования конфигурации Production?
Поясни, что ты имеешь в виду. Тестируют не конфигурацию, а систему (SUT, system under test).
При внесении изменений в конфиругацию SUT тоже меняется, и нужны тесты не то, что эта измененная система работает как ожидается.
Иными словами, если ты внес изменения "теперь эта кнопка зеленая", надо написать соответствующий тест, который убеждается, что кнопка в натуре зеленая (упрощенно, но суть, думаю, понятна).
P>Но в описываемой мною ситуации b не может быть null по определению, в этом случае контейнер бросает вполне читаемое подробное исключение, что такая-то завимисость в таком-то месте не может быть удовлетворена. Снова, возвращаемся к конфигурации и определяем эту зависимость — всё "в рамках".
Без DI framework это же исключение будет брошено сильно раньше. Компилятором. Причем еще и подсвечено в IDE.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, microuser, Вы писали:
M>>Так в данном примере вообще ничего искать не нужно, т.к. при использовании DI экземпляр Bar будет создаваться контейнером и все его зависимости будут так же создаваться контейнером, соответственно нужно посмотреть только код конфигурации этого контейнера.
IT>И что я там увижу? Список имён классов?
M>>Кроме того современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось.
IT>По-поводу умения абстрагироваться я уже выше отписал.
Я же отвечал на конкретный код, который ты привел.
Хорошо, давай модифицируем пример:
class Foo
{
public Foo(Bar b)
{
if (b.Atata == null)
throw ...
}
}
В данном случае при ошибке у нас будет стек трейс в котором будет прекрасно видна последовательность вызовов, поэтому ходить по конструкторам и изучать кто кого вызывал уже будет не нужно.
Предположим что стек трейса у нас нет, но мы знаем что ошибка была и хотим понять как так получилось что Atata == null. Тогда нам нужно нажать Shift+F12 на свойстве Atata, но не на конструкторе. Вот это реальный кейс который часто встречается на практике.
Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример.
SD>Без DI framework это же исключение будет брошено сильно раньше. Компилятором. Причем еще и подсвечено в IDE.
Описал там выше мои мысли. На мой взгляд, это можно рассматривать и как плюс контейнера. Современные реалии на платформах типа джавы/дотнета и так много чего оставляют на рантайм из за рефлешена, кроме конфигурации ioc-контейнеров. Они тут еще самые безобидные.
Здравствуйте, varenikAA, Вы писали:
AA>Здравствуйте, Министр Промышленности, Вы писали:
МП>>а реальная необходимость бывает редко и всего в 2-3 местах системы
AA>Очевидно, что DI это реализация принципа инверсии зависимостей. AA>Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
class A
{
public B RefB;
}
class B
{
public A RefA;
}
void Main()
{
var a = new A(){RefB = new B()};
a.RefB.RefA = a;
}
Циклические зависимости есть. DI в упор не вижу. Ваше слишком общее утверждение опровергнуто.
МП>>>а реальная необходимость бывает редко и всего в 2-3 местах системы
AA>>Очевидно, что DI это реализация принципа инверсии зависимостей. AA>>Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
_>
_>class A
_>{
_> public B RefB;
_>}
_>class B
_>{
_> public A RefA;
_>}
_>void Main()
_>{
_> var a = new A(){RefB = new B()};
_> a.RefB.RefA = a;
_>}
_>
_>Циклические зависимости есть. DI в упор не вижу. Ваше слишком общее утверждение опровергнуто.
да не, очевидно же что под компонентами имелись в виду сборки
то есть в данном случае A в одной сборке, а B — в другой
и какая на какую ссылается
(я столкнулся с такой проблемой в 2005 году первый раз и решил её самостоятельно, не зная термина DI)
но вопрос обсуждения разумеется не об отказе от DI, а только о радикальном ограничении применения DI-фреймворков
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
МП>да не, очевидно же что под компонентами имелись в виду сборки МП>то есть в данном случае A в одной сборке, а B — в другой
Какая разница? замените тип A и B в Ref полях на интерфейс который известен обеим классам, суть от этого не измениться.
Нам впаривают DI и DI фреймворки как нечто сокральное без чего ну никак невозможно писать хороший код.
Здравствуйте, petroa, Вы писали:
p> ·>Компилятор тут притом, что вот такие вещи "современные контейнеры никогда к такой ситуации не приведут, т.к. Bar не сможет быть равен null, будет ошибка при попытке создания Foo о том что не удалось разрешить зависимость Boo и там же в стек трейсе будет видно почему не удалось." ·>в коде без контейнера будут тупо ошибками компиляции. А уж эти современные контейнерные стек-трейсы — это какое-то издевательство над здравым смыслом. p> Но ведь это же плюс контейнера При добавлении в сигнатуру конструктора новой зарегистрированной зависимости она сразу будет передаваться при работе в рамках контейнера.
Если зависимость уже есть в лексическом скопе, то тоже всё сразу заработает. Если нет — надо будет поглядеть почему и сразу заметить, ситуацию когда её не должно быть. Например, нечаянно не создашь циклическую зависимость или не смешаешь слои.
p> Если не зарегистрирована — приложение упадёт с исключением при первом запуске
Не понял в чём плюс. Ошибки в рантайме — плюс?
p> (контейнер сразу строит граф зависимостей, не ждёт ничего в рантайме).
Когда как. Вон там выше чувак вытаскивает зависимости через serviceLocator.Get<Some>().
p> В "ручном" случае при манипулировании зависимостями (изменениями сигнатур конструкторов) мы постоянно меняем вызывающий код, обкладываемся проверками на null как в примере раньше и прочее. Стоила ли статическая проверка этого? Ну, может, в какой-то степени. Но на мой взгляд это никак не стоппер.
Что за вызывающий код? Этот вызывающий код и есть composition root, т.е. описание структуры зависимостей твоего приложения. Отличая дока, кстати.
p> На деле имеем проблему с еще одной идиомой — конфигурация контейнера, о которой нужно знать (а знать сейчас и так дофига чего приходится). Это да. Но я пока пытаюсь сформулировать "мотивировочное решение" для использования ioc-библиотеки.
Да, по сути это целый DSL, притом динамический, и со стандартами плохо.
Здравствуйте, ·, Вы писали:
·>В чём демагогия? Контейнер это глобальная свалка всех объектов приложения, из которой беcконтрольно тянется всё отовсюду.
Как угодно. Если вы уже развесили ярлыки — не вижу смысла в дискуссии.
·>Если зависимость уже есть в лексическом скопе, то тоже всё сразу заработает. Если нет — надо будет поглядеть почему и сразу заметить, ситуацию когда её не должно быть. Например, нечаянно не создашь циклическую зависимость или не смешаешь слои.
Я говорю только в контексте C#/Java. По функциональщине (а терминология по lexical scope я так понимаю сейчас всплыла скорее оттуда) не готов обсуждать, мало опыта. Но если поделишься подробным пояснением, что имел в виду — буду благодарен, это мне интересно.
p>> Если не зарегистрирована — приложение упадёт с исключением при первом запуске ·>Не понял в чём плюс. Ошибки в рантайме — плюс?
Ну, это же надо читать вместе с предыдущим предложением Да, тут будет ошибка в рантайме, но (далее по тексту).
·>Когда как. Вон там выше чувак вытаскивает зависимости через serviceLocator.Get<Some>().
Ну то такое. Не в концепции "конфигурации", которую я описывал выше, это уже на его совести.
·>Что за вызывающий код? Этот вызывающий код и есть composition root, т.е. описание структуры зависимостей твоего приложения. Отличая дока, кстати.
Вот-вот, он. Его придётся частенько дописывать (C#/Java).
·>Да, по сути это целый DSL, притом динамический, и со стандартами плохо.
Здравствуйте, petroa, Вы писали:
p> ·>Если зависимость уже есть в лексическом скопе, то тоже всё сразу заработает. Если нет — надо будет поглядеть почему и сразу заметить, ситуацию когда её не должно быть. Например, нечаянно не создашь циклическую зависимость или не смешаешь слои. p> Я говорю только в контексте C#/Java. По функциональщине (а терминология по lexical scope я так понимаю сейчас всплыла скорее оттуда) не готов обсуждать, мало опыта. Но если поделишься подробным пояснением, что имел в виду — буду благодарен, это мне интересно.
Нет, я имел в виду, что если у тебя есть:
var dao1 = new Dao1(db);
var dao2 = new Dao2(db);
var svc1 = new Svc1(dao1, dao2);
var svc2 = new Svc2(dao2);
То добавить dao1 в Svc2 никаких дополнительных правок не потребует.
Зато в Dao1 добавить svc1 у тебя просто не получится скомпилировать.
p> p>> Если не зарегистрирована — приложение упадёт с исключением при первом запуске p> ·>Не понял в чём плюс. Ошибки в рантайме — плюс? p> Ну, это же надо читать вместе с предыдущим предложением Да, тут будет ошибка в рантайме, но (далее по тексту).
Далее по тексту начинался новый параграф.. я не понял в чём "но".
p> ·>Когда как. Вон там выше чувак вытаскивает зависимости через serviceLocator.Get<Some>(). p> Ну то такое. Не в концепции "конфигурации", которую я описывал выше, это уже на его совести.
Да, но и в том числе и на совести фреймворков, что такой гнокод стимулируют писать как самое простое и очевидное решение.
p> ·>Что за вызывающий код? Этот вызывающий код и есть composition root, т.е. описание структуры зависимостей твоего приложения. Отличая дока, кстати. p> Вот-вот, он. Его придётся частенько дописывать (C#/Java).
По большому счёту современные IDE этот код пишут сами. И это же супер, когда у тебя есть акнуальная дока, притом проверяемая компилятором.
Здравствуйте, barn_czn, Вы писали:
b> Циклические зависимости есть. DI в упор не вижу. Ваше слишком общее утверждение опровергнуто. var a = new A(){RefB = new B()}; — это и есть DI по определению (а конкретно property injection). Альтернативой будет либо
class A
{
public B RefB = new B();
}
либо
class A
{
private B RefB;
public A(ServiceLocator sl)
{
RefB = sl.Get<B>();
}
}
Здравствуйте, Somescout, Вы писали:
IT>>При желании можно абстрагироваться даже от интерфейсов. Но я не буду говорить как, не дай боже вам это понравится. S>Само собой не будете: вы уже который комментарий стесняетесь не то что код приводить, но даже хоть чуть-чуть в конкретику углубляться.
Вроде как код собираешься приводить ты, а не я.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, SkyDance, Вы писали:
IT>>Можно пример тестирования конфигурации Production? SD>Поясни, что ты имеешь в виду. Тестируют не конфигурацию, а систему (SUT, system under test).
Цитирую оригинал
Более того, "конфигурирование" требует ровно того же тестирования, что и "кодирование", и ровно такого же процесса выкатывания изменений.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, microuser, Вы писали:
M>В данном случае при ошибке у нас будет стек трейс
Всё. Дальше можно не продолжать. Ты невнимательно читал выше. Там говорилось о том, что при явном вызове кода без всяких DI фреймоврков с кодом можно легко разобраться ничего не запуская в отладчике.
M>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример.
Пусть передали не null, а поломаный объект.
Если нам не помогут, то мы тоже никого не пощадим.
SD>>Поясни, что ты имеешь в виду. Тестируют не конфигурацию, а систему (SUT, system under test). IT>Цитирую оригинал IT>
IT>Более того, "конфигурирование" требует ровно того же тестирования, что и "кодирование", и ровно такого же процесса выкатывания изменений.
И что тебе непонятно?
У тебя есть файл "исходный код".
У тебя есть файл "конфигурация".
Тебе нужно написать тесты на SUT, которая есть комбинация из "исходный код + конфигурация".
Если система плохо спроектирована (например, позволяет запускать 2 экземпляра, aka staging), эти тесты называются "health checks" (и подразумевают всякие там monitoring/observability/dashboards/alarms). Разумеется, поймать проблему на стадии "health check" тестов очень дорого (намного дороже, чем, скажем, на стадии CI или тем более статического анализа/компиляции).
Здравствуйте, Министр Промышленности, Вы писали:
IT>>>>Да. МП>>>не ну это преребор IT>>Ну блин. Ты то должен был промолчать МП>я стараюсь быть объективным независимо от линии дискуссии
Ты в самом зародыше убил флейм, который выглядел весьма многообещающим.
Если нам не помогут, то мы тоже никого не пощадим.
IT>>>>>Да. МП>>>>не ну это преребор IT>>>Ну блин. Ты то должен был промолчать МП>>я стараюсь быть объективным независимо от линии дискуссии
IT>Ты в самом зародыше убил флейм, который выглядел весьма многообещающим.
и это правильно
я не ради флейма
в солюшенах кстати я стараюсь поступать подобным образом — сокращать весь несодержательный код (кроме примеров песочницы)
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, barn_czn, Вы писали:
_>Дальше, говорят что плюс в том что из любого места я могу получить(создать) любой инстанс. Но простите, в таком случае ваша архитектура превратилась в кучу глобальных переменных где Всё доступно Отовсюду. Как же тогда быть с принципами разграничений? В определенном месте кода должно доступно только то что необходимо, что передали явно, что доступно по ссылочным связям.
В рантайме — да, но в коде получаем отделение интерфейса от реализации. все зависимости "сшиваются" в одном месте.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, varenikAA, Вы писали:
AA>>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов.
IT>Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь.
Здравствуйте, SkyDance, Вы писали:
AA>>Взять теже фабрики, билдеры они тоже зло?
SD>Примененные где не надо — конечно, зло.
по мне так это философия. как только нужно расширить поведение объекта, так сразу возникает необходимость интерфейса и следовательно фабрики.
Кто-то делает это сразу. Кто-то в процессе рефакторинга. Это субъективно.
Да и вообще, оператор new не нужен в принципе. В куче ЯП его нет вообще. К чему эта магия?
Дельфи — :=Create("Alice"), лисп — (make-person :name "Alice"), f# let person = Person {Name = "Alice"}.
Здравствуйте, barn_czn, Вы писали:
_>Здравствуйте, varenikAA, Вы писали:
AA>>Здравствуйте, Министр Промышленности, Вы писали:
МП>>>а реальная необходимость бывает редко и всего в 2-3 местах системы
AA>>Очевидно, что DI это реализация принципа инверсии зависимостей. AA>>Если у вас возникают циклические зависимости между компонентами, то вам не избежать DI.
_>
_>class A
_>{
_> public B RefB;
_>}
_>class B
_>{
_> public A RefA;
_>}
_>void Main()
_>{
_> var a = new A(){RefB = new B()};
_> a.RefB.RefA = a;
_>}
_>
_>Циклические зависимости есть. DI в упор не вижу. Ваше слишком общее утверждение опровергнуто.
Если классы окажутся в разных сборках код уже не соберется. Пока код в одной сборке из-за многопроходного компилятора это собирается.
F# однопроходный и там без явной рекурсии такое объявление не соберется.
Здравствуйте, IT, Вы писали:
M>>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример. IT>Пусть передали не null, а поломаный объект.
Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае?
Здравствуйте, Министр Промышленности, Вы писали:
МП>если инициализация будет объемистой, то вынести в инициализирующий статический helper метод и везде использовать его
А если для создания Dependency1 нужно передать param1 и param2 (скажем, строку подключения и таймаут), а для создания Dependency2 — param3 и param4 (например, имя пользователя и путь к S3), т.е. если Dependency1 и Dependency2 нужно дополнительно конфигурировать?
Как твой helper создаст Dependency1 и Dependency2 в этом случае?
Здравствуйте, varenikAA, Вы писали:
AA>>>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов. IT>>Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь.
AA>Чтобы не зависеть от реализации.
Зачем? Какую проблему это решает?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, ·, Вы писали:
·>Здравствуйте, Sharov, Вы писали:
S>> M>>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример. S>> IT>Пусть передали не null, а поломаный объект. S>> Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае? ·>Я не очень понял твой вопрос, но попробую потелепатить. Различие в том, что в случае с обычным кодом, можно найти откуда конкретно передали просто проанализировав код, find usages. В случае DI-фреймворка ты можешь лишь понять, что пришло из контейнера и всё, и без дебаггера очень сложно разобраться.
Согласен, это я и имел ввиду. Т.е. разница во времени поиска причины, а не в поведении кода, его реакции, на поломанный объект.
За гибкость разработки приходится платить, увы. Серебряной пули нету.
Здравствуйте,
S>>> M>>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример. S>>> IT>Пусть передали не null, а поломаный объект. S>>> Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае? S>·>Я не очень понял твой вопрос, но попробую потелепатить. Различие в том, что в случае с обычным кодом, можно найти откуда конкретно передали просто проанализировав код, find usages. В случае DI-фреймворка ты можешь лишь понять, что пришло из контейнера и всё, и без дебаггера очень сложно разобраться.
S>Согласен, это я и имел ввиду. Т.е. разница во времени поиска причины, а не в поведении кода, его реакции, на поломанный объект. S>За гибкость разработки приходится платить, увы. Серебряной пули нету.
как сказать
с одной стороны, если мы говорим о поломанных объектах, то при правильном программировании инвариантов их возможное существование будет пресекаться на корню. либо в конструкторе, либо в фабрике/билдере. и это безотносительно DI, а просто как правило хорошего тона и clean code.
с другой стороны, если мы перспективу иметь отсечение невалидных объектов (а инварианты могут быть существенно сложнее, чем просто not-null какого-то аргумента) будем рассматривать в контексте инстанцирования синглтонов в рамках какого-то фреймворка DI, то затраты технически будут равны тем, что возникнут и без DI. и там, и там нужно запустить процесс и увидеть, падает он или нет, а на этапе компилирования даже при поддержке некоторых аннотаций, абсолютно все проблемы подобного типа отловить не удастся.
к чему это я (ответ всем тем, кто отметился в этой подветке): не надо сравнивать DI или не-DI там, где проблемы возникают по другим причинам.
и ещё одно небольшое дополнение:
— анализ стек-трейсов причин, почему контейнер DI во время запуска упал из-за нарушения инвариантов синглтонов, даётся легко, если соответствующие исключения снабжены говорящими уникальными текстами, явно указывающими на суть нарушения.
— пользоваться дебаггером для этого — моветон. нужно учиться логировать и читать логи. в первую очередь из-за того, что, когда проблема прилетает из продуктива, никакой дебаггер не поможет, а логи — да.
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, varenikAA, Вы писали:
AA>>>>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов. IT>>>Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь.
AA>>Чтобы не зависеть от реализации.
IT>Зачем? Какую проблему это решает?
Например, командная разработка. Интерфейсы согласованы и лежат в общей сборке. Один работает над клиентом, другой над реализацией интерфейса.
МП>>если инициализация будет объемистой, то вынести в инициализирующий статический helper метод и везде использовать его
Б>А если для создания Dependency1 нужно передать param1 и param2 (скажем, строку подключения и таймаут), а для создания Dependency2 — param3 и param4 (например, имя пользователя и путь к S3), т.е. если Dependency1 и Dependency2 нужно дополнительно конфигурировать?
Б>Как твой helper создаст Dependency1 и Dependency2 в этом случае?
все аргументы которые ему нужны он попросит как входящие аргументы
причем эти все аргументы наверняка будут примитивными типами, типа строк и чисел
чтобы его вызвать нужно будет вначале явно задействовать логику по прочтению конфигурации/обращению к базе данных и т.п. для получения значений
по мне так это структура программы здорового человека
она будет явной, поддерживаемой и развиваемой
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
S>> M>>Просто кейс что при использовании DI в конструктор передали null невозможен в принципе, тут нужен другой пример. S>> IT>Пусть передали не null, а поломаный объект. S>> Передали, и? Чем поведение без DI будет отличаться от поведения с DI в данном случае? ·>Я не очень понял твой вопрос, но попробую потелепатить. Различие в том, что в случае с обычным кодом, можно найти откуда конкретно передали просто проанализировав код, find usages. В случае DI-фреймворка ты можешь лишь понять, что пришло из контейнера и всё, и без дебаггера очень сложно разобраться.
да и с дебагером зачастую приходится повозиться чтобы разобраться
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
AA>>>>>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов. IT>>>>Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь. AA>>>Чтобы не зависеть от реализации.
IT>>Зачем? Какую проблему это решает?
AA>Например, командная разработка. Интерфейсы согласованы и лежат в общей сборке. Один работает над клиентом, другой над реализацией интерфейса.
ойойой
а как же интересно вели командную разработку году так в 2005, когда DI извращения ещё не придумали?
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>ойойой МП>а как же интересно вели командную разработку году так в 2005, когда DI извращения ещё не придумали?
Как бы это лучше сказать? Через одно место вели.
Втоорй профит кстати в том, что клиентский модуль будет работать без перекомпиляции если пришлось пофиксить реализацию.
В случае же с явным созданием зависимого объекта через конструктор придется что? ребилдить весь солюшн.
Ай-йа-яй!
И кстати. нечего не явного не вижу в DI.
services.AddSingleton<IMainService, MainService>(); // Что тут неявного?
services.AddHostedService(prop => prop.GetService<IMainService>() as MainService); // Что тут неявного?
services.AddDbContext<DbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"))); // Что тут неявного?
Зато точка сборки приложения одна а не размазана по разным сборкам.
МП>>ойойой МП>>а как же интересно вели командную разработку году так в 2005, когда DI извращения ещё не придумали? AA>Как бы это лучше сказать? Через одно место вели.
нет, вели приемлемо
а если лид хоть как-то продумывал вопросы параллельной разработки — так и вообще без проблем
например делали временную реализацию того же интерфеса, все методы которой вначале кидают NotImplementedException
AA>Втоорй профит кстати в том, что клиентский модуль будет работать без перекомпиляции если пришлось пофиксить реализацию.
тут уже разобрали этот фактор в обсуждении
когда это реально нужно — принимается как некоторый аргумент "за", хотя и привносит свои риски и затруднения
AA>В случае же с явным созданием зависимого объекта через конструктор придется что? ребилдить весь солюшн. AA>Ай-йа-яй!
ребилдить весь солюшен — это чистое и довольно хорошее решение
кроме случаев когда ребилд занимает слишком много времени — сутки, несколько часов
в описанном вами случае ребилдить придётся только клиентский модуль с его зависимостями
AA>И кстати. нечего не явного не вижу в DI. AA>
AA> services.AddSingleton<IMainService, MainService>(); // Что тут неявного?
AA> services.AddHostedService(prop => prop.GetService<IMainService>() as MainService); // Что тут неявного?
AA> services.AddDbContext<DbContext>(options =>
AA> options.UseSqlServer(
AA> Configuration.GetConnectionString("DefaultConnection"))); // Что тут неявного?
AA>
AA>Зато точка сборки приложения одна а не размазана по разным сборкам.
ну неет
точка сборки приложения действительно лучше бы была одна, а именно — в .sln файле
попытка придать зависимостям характер аспекта явно натянута
нет такой проблемы чтобы вот так вот пахабить код вместо естественной инициализации
и код это не сокращает
и вынос всех конструирований зависимостей в одно место сходно с плохим примером объединения классов по сборкам,
ну не по алфавитному порядку их названий конечно
по принципу: классы, которые имеют параметризованные конструкторы инициализируем не где это надо, а вот здесь!
а с классами, которые скажем имеют события или подтипы — вы ничего не хотите сделать?
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
МП>Они затрудняют распутывание кода, МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>и это не перекрывается гибкостью подстановки mock-объектов
МП>но обнаруживаю ярых адептов этого всего. МП>уже и в вакансиях суют такое требование
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
покажи просто, как ты собираешься поддерживать систему, над которой работали и 10 лет назад (и все эти люди уже уволились) и позавчера пара практикантов, а сегодня надо приделать какую-то фичу тебе и ты эту систему увидел в первый раз
Здравствуйте, Министр Промышленности, Вы писали:
МП>>>так а зачем вообще делать dependency injection? AA>>Уменьшить связанность компонентов.
МП>принимается
Нет не принимается: абстрактная фабрика точно так же уменьшает связность и развязывает зависимости. Ключ здесь — использование интерфейсов вместо конкретных классов.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, gandjustas, Вы писали:
МП>>но обнаруживаю ярых адептов этого всего. МП>>уже и в вакансиях суют такое требование G>Уже? Доброе утро. Погугли что означает D в SOLID
МП>>С моей профессиональной точки зрения DI фреймворки не нужны. G>Надо было писать "недостаточно профессинальной"
Да, похоже на то Недостаточно профессиональной.
Погугли, чем эти термины отличаются.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, takTak, Вы писали:
МП>>кто-то может популярно расписать преимущества либо природу явления популярности? МП>>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
T>покажи просто, как ты собираешься поддерживать систему, над которой работали и 10 лет назад (и все эти люди уже уволились) и позавчера пара практикантов, а сегодня надо приделать какую-то фичу тебе и ты эту систему увидел в первый раз
Встречный закономерный вопрос: как в этом случае поможет DI?
И можно пример кода, пожалуйста.
МП>>>кто-то может популярно расписать преимущества либо природу явления популярности? МП>>>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
T>>покажи просто, как ты собираешься поддерживать систему, над которой работали и 10 лет назад (и все эти люди уже уволились) и позавчера пара практикантов, а сегодня надо приделать какую-то фичу тебе и ты эту систему увидел в первый раз
TG>Встречный закономерный вопрос: как в этом случае поможет DI? TG>И можно пример кода, пожалуйста.
система при DI делится на кучу заменямых компонентов, так что сделанные изменения проще тестировать,
если все прошлые изменения покрыты тестами (а когда есть DI, это сделать в разы проще), то прогонка этих же регрессионных тестов может указать на явные ошибки, когда ни тестов ни нет, то поддержка по становится чем-то вроде ходьбы по канату на высоте 100 метров без страховки
применимо это к так называемым оо — системам, при фунциональщине тесты базируются на иной парадигме, там композиция делается по-другому
T>>все разговоры в этой теме от банального непонимания, для чего придумали DI / IoC ·>Это верно. ·>А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers.
о чём ты вообще? идея юнит-тестирования намного старше веб-серверов... ·>Но это многе не понимают, и даже когда пишут микросервис из 5 классов, втыкают туда контейнер, "ибо надо". Но объяснить для чего — не могут.
T>>ну так вот тебе надо ПРОТЕСТИРОВАТЬ, что схема базы данных, используемая твоим кодом, актуальна, например, ·>А ещё многие не понимают разницу между выделением интерфейсов, IoC, DI, контейнерами.
T>>как ты с твоим этим кодом будешь писать код, который это проверяет? ·>
·>var mainService = new MainService(new InMemoryProvider(), new Configuration(), new IdentityProvider(Thread.Current.Identity), new FileManager() , new EmailSerice(new SmtrAddress(25.145.789), etc.));
·>...
·>testThis(mainService);
·>testThat(mainService);
·>
этот код показывает, что ты ни разу правильно не написал тестового кода с использованием DI, правильно было бы так:
·>
>Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надо
·>var mainService = Container.Resolve<MainService> ();
·>...
·>testThis(mainService);
·>testThat(mainService);
·>
Здравствуйте, takTak, Вы писали:
T>>>все разговоры в этой теме от банального непонимания, для чего придумали DI / IoC T>·>Это верно. T>·>А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers. T> о чём ты вообще? идея юнит-тестирования намного старше веб-серверов...
"веб-сервер" я не говорил, не приписывай мне ложные высказывания.
Для юнит-тестирования контейнер не нужен тоже, чаще даже вреден. Нужен DI.
T>>>как ты с твоим этим кодом будешь писать код, который это проверяет?
T>·>var mainService = new MainService(new InMemoryProvider(), new Configuration(), new IdentityProvider(Thread.Current.Identity), new FileManager() , new EmailSerice(new SmtrAddress(25.145.789), etc.));
T>·>...
Ты предлагаешь посылать письма и писать файлы из тестов? Особенно весело бороться в тестах с многопоточкой. Нет, в тестахм добрая половина этих зависимостей будут тестовые реализации.
А для второй половины — если же ты хочешь потестировать интеграцию MainService с какими-то фиксированными зависимостями, которые так же используются и в реальном приложении тоже, просто делаешь классическую композицию и у тебя получается переиспользуемый модуль со своими зависимостями, например что-то типа такого:
class MainServiceModule
{
MainService service;
MainServiceModule(DbProvider dbProvider, EmailService emailService)
{
service = new MainService(dbProvider, configuration, new IdentityProvider(Thread.Current.Identity), new FileManager(), emailService);
}
}
T>этот код показывает, что ты ни разу правильно не написал тестового кода с использованием DI, правильно было бы так:
Такой код я писал, конечно. Но с тех пор кое-чему научился.
T>>Container.Register<InMemoryProvider, DbProvider> ()
Это не DI, это контейнер. Разберись в конце-концов что такое DI.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
T>>>>все разговоры в этой теме от банального непонимания, для чего придумали DI / IoC T>>·>Это верно. T>>·>А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers. T>> о чём ты вообще? идея юнит-тестирования намного старше веб-серверов... ·>"веб-сервер" я не говорил, не приписывай мне ложные высказывания.
ой, насмешил... твой сервер приложений ещё моложе, чем веб-сервера ·>Для юнит-тестирования контейнер не нужен тоже, чаще даже вреден. Нужен DI.
ну ка покажи, как ты собираешься делать DI без Ioc контейнера
T>>>>как ты с твоим этим кодом будешь писать код, который это проверяет? ·>
T>>·>var mainService = new MainService(new InMemoryProvider(), new Configuration(), new IdentityProvider(Thread.Current.Identity), new FileManager() , new EmailSerice(new SmtrAddress(25.145.789), etc.));
T>>·>...
·>
·>Ты предлагаешь посылать письма и писать файлы из тестов? Особенно весело бороться в тестах с многопоточкой. Нет, в тестахм добрая половина этих зависимостей будут тестовые реализации.
ты сам назвал сервис "Main", а теперь на меня обижаешься ? в том-то и дело, что в одном случае у тебя его в твоём тесте даже инициировать толком не получится, а в моём варианте инициализация и тестового и продуктивного кода выглядит почти одинаково, с одним лишь отличием, что я подменяю то, что тестирую и вызываю тем, что меня в тесте интересует
·>А для второй половины — если же ты хочешь потестировать интеграцию MainService с какими-то фиксированными зависимостями, которые так же используются и в реальном приложении тоже, просто делаешь классическую композицию и у тебя получается переиспользуемый модуль со своими зависимостями, например что-то типа такого: ·>
·>class MainServiceModule
·>{
·> MainService service;
·> MainServiceModule(DbProvider dbProvider, EmailService emailService)
·> {
·> service = new MainService(dbProvider, configuration, new IdentityProvider(Thread.Current.Identity), new FileManager(), emailService);
·> }
·>}
·>
ну это уже тонкости, как ты там назовёшь: модуль , сервис или ещё как...
T>>этот код показывает, что ты ни разу правильно не написал тестового кода с использованием DI, правильно было бы так: ·>Такой код я писал, конечно. Но с тех пор кое-чему научился.
T>>>Container.Register<InMemoryProvider, DbProvider> () ·>Это не DI, это контейнер. Разберись в конце-концов что такое DI.
так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим
Здравствуйте, takTak, Вы писали:
T>>>·>А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers. T>>> о чём ты вообще? идея юнит-тестирования намного старше веб-серверов... T>·>"веб-сервер" я не говорил, не приписывай мне ложные высказывания. T>ой, насмешил... твой сервер приложений ещё моложе, чем веб-сервера
Ага, и причём тут идея юнит-тестирования? Ты споришь с сам с собой. Я тебе не мешаю?
T>·>Для юнит-тестирования контейнер не нужен тоже, чаще даже вреден. Нужен DI. T>ну ка покажи, как ты собираешься делать DI без Ioc контейнера Тут уже раз сто показывали. Вот ещё раз.
T>·>Ты предлагаешь посылать письма и писать файлы из тестов? Особенно весело бороться в тестах с многопоточкой. Нет, в тестахм добрая половина этих зависимостей будут тестовые реализации. T>ты сам назвал сервис "Main", а теперь на меня обижаешься ?
У тебя опять проблемы с чтением. Сервис так не я назвал, а varenikAA тут
.
T>в том-то и дело, что в одном случае у тебя его в твоём тесте даже инициировать толком не получится,
У тебя не получится, у других получится. Если не будешь отключать мозг, то и ты научишься, это не сложно.
T>а в моём варианте инициализация и тестового и продуктивного кода выглядит почти одинаково, с одним лишь отличием, что я подменяю то, что тестирую и вызываю тем, что меня в тесте интересует
А в моём варианте инициализация будет вызывать тот же самый код.
T>ну это уже тонкости, как ты там назовёшь: модуль , сервис или ещё как...
Ну да, суть в том, что код для композиции нескольких компонент — это тоже самый обыкновенный код, а не контейнерная магия.
T>>>>Container.Register<InMemoryProvider, DbProvider> () T>·>Это не DI, это контейнер. Разберись в конце-концов что такое DI. T>так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим
Я уже написал тут
T>>>>·>А придумали контейнеры для сборки монструозных монолитных enterprise приложений из крупных отдельно поставляемых компонент, плагинной архитектуры, внутри так называемых application servers. T>>>> о чём ты вообще? идея юнит-тестирования намного старше веб-серверов... T>>·>"веб-сервер" я не говорил, не приписывай мне ложные высказывания. T>>ой, насмешил... твой сервер приложений ещё моложе, чем веб-сервера ·>Ага, и причём тут идея юнит-тестирования? Ты споришь с сам с собой. Я тебе не мешаю?
итак, возвращаемся к первому предложению: юнит-тестирование возникло до появления серверов приложений, так что облегчающие юнит-тесторование DI не имеет к серверам приложений никакого отношения
T>>·>Для юнит-тестирования контейнер не нужен тоже, чаще даже вреден. Нужен DI. T>>ну ка покажи, как ты собираешься делать DI без Ioc контейнера ·> Тут уже раз сто показывали. Вот ещё раз.
ты на своём коде покажи, "а вот дядя знает" в другом возрасте употребляют
T>>·>Ты предлагаешь посылать письма и писать файлы из тестов? Особенно весело бороться в тестах с многопоточкой. Нет, в тестахм добрая половина этих зависимостей будут тестовые реализации. T>>ты сам назвал сервис "Main", а теперь на меня обижаешься ? ·>У тебя опять проблемы с чтением. Сервис так не я назвал, а varenikAA тут
.
пиши в спортлото
T>>в том-то и дело, что в одном случае у тебя его в твоём тесте даже инициировать толком не получится, ·>У тебя не получится, у других получится. Если не будешь отключать мозг, то и ты научишься, это не сложно.
не спорю, в этом ты- мастак
T>>а в моём варианте инициализация и тестового и продуктивного кода выглядит почти одинаково, с одним лишь отличием, что я подменяю то, что тестирую и вызываю тем, что меня в тесте интересует ·>А в моём варианте инициализация будет вызывать тот же самый код.
так ты точно уверен, что ты не сам с собой общаешься ?
T>>ну это уже тонкости, как ты там назовёшь: модуль , сервис или ещё как... ·>Ну да, суть в том, что код для композиции нескольких компонент — это тоже самый обыкновенный код, а не контейнерная магия.
T>>>>>Container.Register<InMemoryProvider, DbProvider> () T>>·>Это не DI, это контейнер. Разберись в конце-концов что такое DI. T>>так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим ·>Я уже написал тут
Здравствуйте, takTak, Вы писали:
T>·>Ага, и причём тут идея юнит-тестирования? Ты споришь с сам с собой. Я тебе не мешаю? T>итак, возвращаемся к первому предложению: юнит-тестирование возникло до появления серверов приложений, так что облегчающие юнит-тесторование DI не имеет к серверам приложений никакого отношения
Да. И? Я ничего другого и не утверждал. Ты просто путаешь DI и DI-контейнеры. А вот контейнеры и фреймворки имеют прямое отношение к серверам приложений ибо идея пошла оттуда.
T>>>ну ка покажи, как ты собираешься делать DI без Ioc контейнера T>·> Тут уже раз сто показывали. Вот ещё раз. T>ты на своём коде покажи, "а вот дядя знает" в другом возрасте употребляют
Ещё раз: вот тут
ванильный DI, без контейнеров и интерфейсы тоже не обязательны.
T>>>ты сам назвал сервис "Main", а теперь на меня обижаешься ? T>·>У тебя опять проблемы с чтением. Сервис так не я назвал, а varenikAA тут
. T>пиши в спортлото
Действительно, тебе писать бесполезно, ты всё равно не читаешь, только хамишь.
T>>>в том-то и дело, что в одном случае у тебя его в твоём тесте даже инициировать толком не получится, T>·>У тебя не получится, у других получится. Если не будешь отключать мозг, то и ты научишься, это не сложно. T>не спорю, в этом ты- мастак
Да, спасибо. И тебе того же желаю.
T>·>А в моём варианте инициализация будет вызывать тот же самый код. T>так ты точно уверен, что ты не сам с собой общаешься ?
Да, уверен, просто ты не слушаешь.
T>>>так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим T>·>Я уже написал тут
и выше. Если кода мало и он примитивный это не означает, что он какой-то неправильный, тебе просто непривычно после контейнеров-то. T>т.е. ты делаешь DI без интерфейсов? ты точно свою ссылку из википедии прочитал?
Да, прочитал. Для DI/CI интерфейсы не нужны. И контейнеры тоже. А интерфейсы _нужны_ только для так называемого частного случая Interface Injection (который в этом обсуждении ни разу не упоминался). И ты прочитай, рекомендую.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, varenikAA, Вы писали:
AA>>>>>Понял, а то у ТС вообще неопнятно на что он жалуется. ну это субъективно. так то во всех учебниках учат что надо избегать явного создания объектов. IT>>>>Они не учат почему этого нужно избегать? Если нет, то ввыкинь такие учебники на помоечку. Впрочем, если да, то тоже выкинь. AA>>>Чтобы не зависеть от реализации. IT>>Зачем? Какую проблему это решает? AA>Например, командная разработка. Интерфейсы согласованы и лежат в общей сборке. Один работает над клиентом, другой над реализацией интерфейса.
А проблема в чём? Зачем тут интерфейсы? К тому же, насколько мне известно, командная разработка это больше об SMS, чем об интерфейсах.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, C0s, Вы писали:
C0s>с одной стороны, если мы говорим о поломанных объектах, то при правильном программировании инвариантов их возможное существование будет пресекаться на корню. либо в конструкторе, либо в фабрике/билдере. и это безотносительно DI, а просто как правило хорошего тона и clean code.
Да, да. Особенно в сферическом вакууме или на примитивных проектах, типа PetShop.
C0s>к чему это я (ответ всем тем, кто отметился в этой подветке): не надо сравнивать DI или не-DI там, где проблемы возникают по другим причинам.
Никто и не сравнивает DI или не-DI. Сравнивают DI фреймворк или не-DI фреймворк.
C0s>- пользоваться дебаггером для этого — моветон. нужно учиться логировать и читать логи. в первую очередь из-за того, что, когда проблема прилетает из продуктива, никакой дебаггер не поможет, а логи — да.
У меня процессы работают всю ночь. Если логировать каждую строчку кода в подробностях и с прилежанием, то место на диске с логами закончится очень быстро. А так да, логируем, куда девваться.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>Да, да. Особенно в сферическом вакууме или на примитивных проектах, типа PetShop.
правила хорошего тона и clean code не зависят от размера проекта, и полезны сразу и систематически — до того, как проект начнёт внезапно разрастаться. более того, обычный подготовленный специалист это делает на автомате без дополнительных трудозатрат. а наличие неподготовленного — сам понимаешь, вроде и реальность, но является лишь организационной проблемой, а не инженерной.
C0s>>к чему это я (ответ всем тем, кто отметился в этой подветке): не надо сравнивать DI или не-DI там, где проблемы возникают по другим причинам.
IT>Никто и не сравнивает DI или не-DI. Сравнивают DI фреймворк или не-DI фреймворк.
раз уж мы тут, то DI-фреймворк важен тем, что для правильного применения сразу требует хорошего чувства объектного построения, проектирования надёжных и устойчивых моделей, а также инверсии зависимостей. без оного этого тоже можно добиться, но только палкой или кнутом. кроме того, фреймворки типа spring являются сами по себе примерами хорошего кода, содержат массу готовых велосипедов с гибкой настройкой нужных скоростей на любые направления движения.
IT>У меня процессы работают всю ночь. Если логировать каждую строчку кода в подробностях и с прилежанием, то место на диске с логами закончится очень быстро. А так да, логируем, куда девваться.
не надо логировать постоянно, достаточно возможности увеличивать или уменьшать подробность логирования без останова процесса. нашли проблему пользователи — включаем логи, просим повторить, снимаем их и анализируем.
T>>·>Ага, и причём тут идея юнит-тестирования? Ты споришь с сам с собой. Я тебе не мешаю? T>>итак, возвращаемся к первому предложению: юнит-тестирование возникло до появления серверов приложений, так что облегчающие юнит-тесторование DI не имеет к серверам приложений никакого отношения ·>Да. И? Я ничего другого и не утверждал. Ты просто путаешь DI и DI-контейнеры.
просто я никогда не пользовался никакими серверами приложений: контейнеры были для меня именно помощью в написании проложений, так чтобы получебнное прлижение просто тестировалось бы,
если же тестов не предвидится, то и никакого смысла в том, чтобы композицию обьектов прозводить через DI, я не вижу, тут я на стороне того, кто эту ветку начал
ты начал использовать DI в контексте серверов приложений, причём, наверное, и без всяких тестов, имхо такой способ композиции приложения довольно прост, но какого-то выиграша от такого подхода , на самом деле, не видно
·>А вот контейнеры и фреймворки имеют прямое отношение к серверам приложений ибо идея пошла оттуда.
я тебе уже в который раз повторяю, что идея пошла от юнит-тестирования, вероятно, создатели серверов приложений были в курсе того, что необходимо предложить пользователям возможность юнит-тестирования, и оно тут в списке первично
T>>>>ну ка покажи, как ты собираешься делать DI без Ioc контейнера T>>·> Тут уже раз сто показывали. Вот ещё раз. T>>ты на своём коде покажи, "а вот дядя знает" в другом возрасте употребляют ·>Ещё раз: вот тут
ванильный DI, без контейнеров и интерфейсы тоже не обязательны.
то, что ты называешь "ванильным" , создаёт жёсткие зависимости между компонентами, что делает практически невозможным изолированное тестирование
T>>·>А в моём варианте инициализация будет вызывать тот же самый код. T>>так ты точно уверен, что ты не сам с собой общаешься ? ·>Да, уверен, просто ты не слушаешь.
ты первый начал
T>>>>так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим T>>·>Я уже написал тут
и выше. Если кода мало и он примитивный это не означает, что он какой-то неправильный, тебе просто непривычно после контейнеров-то. T>>т.е. ты делаешь DI без интерфейсов? ты точно свою ссылку из википедии прочитал? ·>Да, прочитал. Для DI/CI интерфейсы не нужны. И контейнеры тоже. А интерфейсы _нужны_ только для так называемого частного случая Interface Injection (который в этом обсуждении ни разу не упоминался). И ты прочитай, рекомендую.
этот , как ты говоришь , "частный случай" — для меня единственно возможное и целесобразное применение , иначе смысла просто нет
Здравствуйте, takTak, Вы писали:
T>>>так давай, покажи написанный на твоёй коленке DI без контейнера, мы его оценим IT>>Похоже ты реально путаешь DI и контейнеры. T>я не вижу никакого смысла в DI без IoC контейнерa, это тоже самое, что если есть буква, но "слов ещё не завезли"
Я понял.
Если нам не помогут, то мы тоже никого не пощадим.
·>Контейнеры относятся к апп-серверам. Там возникла идея "а давайте положим в контейнер транзакционные менеджеры, коннекты к базам, бины, и будем их извлекать и связывать автомагически". ·>Не мешай это всё в кучу.
я тебе ещё раз говорю: ты со своей явой ни туда смотришь, контейнеров под дотнетом сотни, и никакими серверами приложений там даже близко не пахнет, как и в многих других языках
·>Мне надоело писать одно и то же. Ты игнорируешь эту разницу, тебя трудно понимать. Давай ты в следующем ответе опишешь своё понимание разницы DI и контейнеров, иначе смысла вести беседу я не вижу.
тебе твоя ява застилает глаза, контейнер не обязан быть сложным
T>>то, что ты называешь "ванильным" , создаёт жёсткие зависимости между компонентами, ·>Не создаёт.
T>>что делает практически невозможным изолированное тестирование ·>Вот с контейнером обеспечить изолированное тестирование гораздо сложнее. Ибо вот ты написал ·>
·>Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надо
·>var mainService = Container.Resolve<MainService> ();
·>
·>У тебя Container может неявно втащить много чего и изолировать сложно.
ну так я регистрирую то, что меня интересует, и разрешаю и использую только то, что меня в тесте интересует
T>>этот , как ты говоришь , "частный случай" — для меня единственно возможное и целесобразное применение , иначе смысла просто нет ·>Ты издеваешься что-ли? Ты статью так и не прочитал. И нифига не понимаешь что такое Interface Injection.
в моём мире существует разве что Constructor Injection/ Property Injection
Здравствуйте, takTak, Вы писали:
T>·>Не мешай это всё в кучу. T>я тебе ещё раз говорю: ты со своей явой ни туда смотришь, контейнеров под дотнетом сотни,
И большинство из них позаимствовано из Явы.
T> и никакими серверами приложений там даже близко не пахнет, как и в многих других языках
IIS же.
T>·>Мне надоело писать одно и то же. Ты игнорируешь эту разницу, тебя трудно понимать. Давай ты в следующем ответе опишешь своё понимание разницы DI и контейнеров, иначе смысла вести беседу я не вижу. T>тебе твоя ява застилает глаза, контейнер не обязан быть сложным
Тебе твой дотнет застилает глаза, контейнер не обязан быть.
T>>>что делает практически невозможным изолированное тестирование T>·>Вот с контейнером обеспечить изолированное тестирование гораздо сложнее. Ибо вот ты написал T>·>
T>·>Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надо
T>·>var mainService = Container.Resolve<MainService> ();
T>·>
T>·>У тебя Container может неявно втащить много чего и изолировать сложно. T>ну так я регистрирую то, что меня интересует, и разрешаю и использую только то, что меня в тесте интересует
Ты уж определись. Либо "мне трогать не надо", либо "регистрирую то, что меня интересует". Т.к. ты либо явно должен указать, что тебя интересует, либо это будет описано где-то неявно и тогда будет лезть что попадётся.
T>>>этот , как ты говоришь , "частный случай" — для меня единственно возможное и целесобразное применение , иначе смысла просто нет T>·>Ты издеваешься что-ли? Ты статью так и не прочитал. И нифига не понимаешь что такое Interface Injection. T>в моём мире существует разве что Constructor Injection/ Property Injection
И для них интерфейсы использовать не нужно (но можно, к DI это ортогонально). ЧТД.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
T>>·>Не мешай это всё в кучу. T>>я тебе ещё раз говорю: ты со своей явой ни туда смотришь, контейнеров под дотнетом сотни, ·>И большинство из них позаимствовано из Явы.
я даже не знаю, о чём ты.., ты , наверное, за словом "контейнер" понимаешь лишь j2ee container, ну так вот "контейнер" — это всего лишь "контейнер", не более, современные IoC контейнеры в отличных от явы языках очень легковесны
T>> и никакими серверами приложений там даже близко не пахнет, как и в многих других языках ·>IIS же.
это скорее веб север типа tomcat или apache
T>>·>Мне надоело писать одно и то же. Ты игнорируешь эту разницу, тебя трудно понимать. Давай ты в следующем ответе опишешь своё понимание разницы DI и контейнеров, иначе смысла вести беседу я не вижу. T>>тебе твоя ява застилает глаза, контейнер не обязан быть сложным ·>Тебе твой дотнет застилает глаза, контейнер не обязан быть.
легковесный IoC контейнер безумно облегчает жизнь
T>>>>что делает практически невозможным изолированное тестирование T>>·>Вот с контейнером обеспечить изолированное тестирование гораздо сложнее. Ибо вот ты написал T>>·>
T>>·>Container.Register<InMemoryProvider, IDbProvider> (); // и 5 других зависимостей мне трогать не надо
T>>·>var mainService = Container.Resolve<MainService> ();
T>>·>
T>>·>У тебя Container может неявно втащить много чего и изолировать сложно. T>>ну так я регистрирую то, что меня интересует, и разрешаю и использую только то, что меня в тесте интересует ·>Ты уж определись. Либо "мне трогать не надо", либо "регистрирую то, что меня интересует". Т.к. ты либо явно должен указать, что тебя интересует, либо это будет описано где-то неявно и тогда будет лезть что попадётся.
так это тоже элементарно: если то, что по дефолту подтащится, для инициации теста прокатит, но в самом тесте вызвано не будет, то мне достаточно подменить только то, что я собираюсь тестировать
T>>>>этот , как ты говоришь , "частный случай" — для меня единственно возможное и целесобразное применение , иначе смысла просто нет T>>·>Ты издеваешься что-ли? Ты статью так и не прочитал. И нифига не понимаешь что такое Interface Injection. T>>в моём мире существует разве что Constructor Injection/ Property Injection ·>И для них интерфейсы использовать не нужно (но можно, к DI это ортогонально). ЧТД.
смысл использования интерфейса в оо — это double dispatch, и если у тебя имеется код, который десятилетиями не меняется, то и смылся ни в тестах ни в интерфейсе, ни в DI для него нет
Здравствуйте, IT, Вы писали:
AA>>Например, командная разработка. Интерфейсы согласованы и лежат в общей сборке. Один работает над клиентом, другой над реализацией интерфейса.
IT>А проблема в чём? Зачем тут интерфейсы? К тому же, насколько мне известно, командная разработка это больше об SMS, чем об интерфейсах.
Как минимум в том, что если клиент явно создает экземпляр класса, то ему придется ждать рабочую версию библиотеки.
Если же библиотека будет поломона, то разработка клиента также встанет. Вы не сможете обновить клиента, пока у вас нет новой версии библиотеки.
Здравствуйте, ·, Вы писали:
·>Здравствуйте, varenikAA, Вы писали:
AA>>// Что тут неявного? ·>Непонятно кто от кого и как зависит.
AA>>Зато точка сборки приложения одна а не размазана по разным сборкам. ·>Просто делай то же самое, но без контейнера. Если я правильно разгадал твой код:
·>
·>var dbOptions = new DbOptions()
·> .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
·>var dbContext = new DbContext(dbOptions);
·>var mainService = new MainService(dbContext);
·>services.AddHostedService(mainService);
·>
·>Код внезапно стал проще — никаких лямбд, генериков, рефлексии, даункастов. Можно использовать IDE вовсю — find usages, declarations, использовать рефакторинги.
Серьезно? "найти все ссылки" работает прекрасно и в первом случае.
Рефакторинг? Назовите хоть одну проблему.
DI никуда ни делся, только добавили кучу не нужных new.
От интерфейсов м механизма DI все равно никуда спрятаться в клиентском коде, хотя для получения ссылки, хоть для создания локального скоупа,
либо придется всюду делать ссылку на реализацию. Это отлично работает если у вас развитые средства рефакторинга, но это признак сильных зависимостей.
Вообще если посмотреть на техники ФП, то там все на интерфейсах(любая функция по сути это он и есть),
только за счет возможностей ЯП еще и вызов зависимости выносится за скобки, т.к. есть возможность создавать
вычислительные выражения.
"польза" Dependency Injection для ЯП типа C# очевидна. Не понимаю, почему нужно изобретать велосипед, когда есть отличная билт-ин реализация в коре(быть может вы все еще работает на легаси, тогда — да, только лисопед)?
Здравствуйте, varenikAA, Вы писали:
AA>Серьезно? "найти все ссылки" работает прекрасно и в первом случае. AA>Рефакторинг? Назовите хоть одну проблему. AA>DI никуда ни делся, только добавили кучу не нужных new. AA>От интерфейсов м механизма DI все равно никуда спрятаться в клиентском коде, хотя для получения ссылки, хоть для создания локального скоупа, AA>либо придется всюду делать ссылку на реализацию. Это отлично работает если у вас развитые средства рефакторинга, но это признак сильных зависимостей.
AA>Вообще если посмотреть на техники ФП, то там все на интерфейсах(любая функция по сути это он и есть), AA>только за счет возможностей ЯП еще и вызов зависимости выносится за скобки, т.к. есть возможность создавать AA>вычислительные выражения.
AA>"польза" Dependency Injection для ЯП типа C# очевидна. Не понимаю, почему нужно изобретать велосипед, когда есть отличная билт-ин реализация в коре(быть может вы все еще работает на легаси, тогда — да, только лисопед)?
Вроде, про полезность интерфейсов никто не спорит.
Изначальная мысль была: "Просто делай то же самое, но без контейнера."
Здравствуйте, takTak, Вы писали:
T>система при DI делится на кучу заменямых компонентов, так что сделанные изменения проще тестировать, T>если все прошлые изменения покрыты тестами (а когда есть DI, это сделать в разы проще), то прогонка этих же регрессионных тестов может указать на явные ошибки, когда ни тестов ни нет, то поддержка по становится чем-то вроде ходьбы по канату на высоте 100 метров без страховки T>применимо это к так называемым оо — системам, при фунциональщине тесты базируются на иной парадигме, там композиция делается по-другому
Понял. Спасибо. Вопрос: в описываемом случае с легаси ограничитесь извлечением интерфейсов или еще и DI-контейнер используете?
Здравствуйте, TG, Вы писали:
TG>Вроде, про полезность интерфейсов никто не спорит. TG>Изначальная мысль была: "Просто делай то же самое, но без контейнера."
Если разным частям приложения нужны общие объекты, то без контейнера не обойтись, просто это будет велосипед.
Конечно, если это хеловорд, то можно без DI-контейнера, но вопрос как раз о зависимостях как таковых, а не о контейнере который упрощает работу с зависимостями (см тему).
Б>·>А ещё многие не понимают разницу между выделением интерфейсов, IoC, DI, контейнерами. Б>Да, в этом топике идет спор и про DI vs не-DI, и про "Pure DI" vs "DI Container".
я поднимал вопрос лишь о "Pure DI" vs "DI Container" — "DI-фреймворки" в моей формулировке
да и для "Pure DI" вообще не следовало придумывать отдельного названия, настолько это примитивно
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, Министр Промышленности, Вы писали:
МП>я поднимал вопрос лишь о "Pure DI" vs "DI Container" — "DI-фреймворки" в моей формулировке МП>да и для "Pure DI" вообще не следовало придумывать отдельного названия, настолько это примитивно
Ну, здесь еще спор про "Использование DI в частных случаях" и "DI как принцип построения всего приложений"
Во втором случае для создания объектов не нужны никакие хелперы и статические методы (в самих классах)
AA>·>var dbOptions = new DbOptions()
AA>·> .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
AA>·>var dbContext = new DbContext(dbOptions);
AA>·>var mainService = new MainService(dbContext);
AA>·>services.AddHostedService(mainService);
AA>·>
AA>·>Код внезапно стал проще — никаких лямбд, генериков, рефлексии, даункастов. Можно использовать IDE вовсю — find usages, declarations, использовать рефакторинги.
AA>Серьезно? "найти все ссылки" работает прекрасно и в первом случае. AA>Рефакторинг? Назовите хоть одну проблему.
да это уже описано несколько раз в теме, в том числе и мной
AA>DI никуда ни делся, только добавили кучу не нужных new.
а у нас какие-то проблемы с ключевым словом new ?
я пропустил новые веяния может это теперь как goto?..
а с var или int сейчас всё на рынке норм? for?
AA>Это отлично работает если у вас развитые средства рефакторинга, но это признак сильных зависимостей.
да средства рефакторинга довольно развитые
я подсел на решарпер
но и сама студия подтягивается к нему постепенно
работать вообще последние годы стало даже приятно
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, ·, Вы писали:
AA>> Конечно, если это хеловорд, то можно без DI-контейнера, но вопрос как раз о зависимостях как таковых, а не о контейнере который упрощает работу с зависимостями (см тему). ·>Так продемонстрируй упрощение. Мой фрагмент кода получился проще твоего.
Простота вещь относительная, если катану в руках никогда не держал, то можно и ногу себе отрубить.
Здравствуйте, Министр Промышленности, Вы писали:
МП>да это уже описано несколько раз в теме, в том числе и мной МП>а у нас какие-то проблемы с ключевым словом new ? МП>я пропустил новые веяния может это теперь как goto?.. МП>а с var или int сейчас всё на рынке норм? for? МП>да средства рефакторинга довольно развитые МП>я подсел на решарпер МП>но и сама студия подтягивается к нему постепенно МП>работать вообще последние годы стало даже приятно
Тема высосана из пальца. "О пользе Dependency Injection". Зачем писать в заголовке одно, а в теме другое?..
В чём особоый смысл new, необходимость в прикладном коде? Хотя быть может это другая тема...
def dict = Dictionary();
Если нет new то появляется возможность передавать конструктор в качестве параметра, т.к. это обычная функция возвращающая объект.
Nemerle отказался от него и получил почти ту же мощь, что и CL.
В F# почему-то до сих пор раздражающее сообщение от компилятора:
warning FS0760: Рекомендуется создавать объекты, поддерживающие интерфейс IDisposable с помощью "new Type(args)", а не "Type(args)" или "Type" в качестве значения функции, представляющего конструктор; это делается для того, чтобы указать, что ресурсы могут принадлежать созданному значению.
Так и не узнал в чем сакральный смысл этого оператора.
Против goto в C# ничего против не имею, т.к. его возможности ограничены локальной областью.
Если я использую asp.net core то из коробки получаю кучу возможностей т.к. это ФРЭЙМВОРК с готовыми решениями.
Если я как вы предлагаете все это выброшу на помойку, то мне придется все изобретать самому, т.к. DI-контейнер — зло,
EF — зло, MS — зло,... и т.д.
Отличный способ завязать все "на себя". Но не лучший в перспективе поддержки такой поделки.
Здравствуйте, Министр Промышленности, Вы писали:
МП>Они затрудняют распутывание кода,
Есть такое. Но это скорее проблема реализации DI.
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
А вот это точно полная чушь. Прекрасно рефакторю код напичканный DI с помощью Райдера. Никаких проблем нет. Наоборот мощные рефакторинги Райдера помогают в этом.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
МП>>С моей профессиональной точки зрения DI фреймворки не нужны. VD>Я их тоже недолюбливаю, но понимаю их выгоды (как и недостатки). VD>Есть случаи когда DI вообще удобное решение. Например, когда сам продукт — это расширяемая среда вроде MS VS. Представить скажем подсветку кода в вид инжектящегося компонента удобно и красиво. Не надо писать левый код регистрации. Все выглядит декларативно и красиво. VD>То что DI решает проблему написания кучи кода создающего объекты и передающего их в другие конструкторы тоже верно. Иногда объектов может быть очень много. Но есть тут и минусы. Код завязанный на коркретный DI хрен перенесешь в другой проект. Иногда DI заставляет вводить лишние абстракции. Скажем вводить интерфейсы там где у них никогда не будет более одной реализации. Иногда DI приводит к тому, что хрен поймешь, что за тип к тебе в реальности приехал и где та магия, которая к этому привела. VD>Многие проблемы DI происходят от того, что DI-фрэймворки динамические, создают граф зависимостей в рантайме. Если бы зависимости разрешались бы статически, при компиляции море проблем DI ушло бы.
ну вот грамотный ответ, спасибо, как и ожидалось от Розового Слона
я в общем согласен
VD>Ну, а по жизни все просто. Вот есть в продукте DI и все через него сделано и ты вынужден использовать его и писать в соответствующем стиле. А если тебя свой проект, то выбор — это архитектурное решение.
если использование DI-фреймворка до моего прихода в проект внедрёно не слишком плотно, я его выкорчёвываю
VD>Лично я обычно с неохотой иду на использование DI. Не поверите, но код отлично можно писать в процедурном стиле.
я-то поверю, и сам так пишу (ну процедурный стиль + ООП конечно)
Землю — крестьянам !
Рабочим — работу !
Елбасы — колбасы !
Здравствуйте, varenikAA, Вы писали:
AA>>> Конечно, если это хеловорд, то можно без DI-контейнера, но вопрос как раз о зависимостях как таковых, а не о контейнере который упрощает работу с зависимостями (см тему). AA>·>Так продемонстрируй упрощение. Мой фрагмент кода получился проще твоего. AA>Простота вещь относительная, если катану в руках никогда не держал, то можно и ногу себе отрубить.
Ты намекаешь, что у меня мало опыта с контейнерами. Это не так, совсем наоборот, и даже сейчас у приходится с бангалорским кодом возиться, более того я научился писать код и без контейнера. А ты сам говоришь, что не умеешь: "...то без контейнера не обойтись".
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Ты намекаешь, что у меня мало опыта с контейнерами. Это не так, совсем наоборот, и даже сейчас у приходится с бангалорским кодом возиться, более того я научился писать код и без контейнера. А ты сам говоришь, что не умеешь: "...то без контейнера не обойтись".
Поздравляю! Что вы мне тут пытаетесь доказать? Что умеете сами управлять зависимостями? Хахаха!
AA>·>Ты намекаешь, что у меня мало опыта с контейнерами. Это не так, совсем наоборот, и даже сейчас у приходится с бангалорским кодом возиться, более того я научился писать код и без контейнера. А ты сам говоришь, что не умеешь: "...то без контейнера не обойтись".
AA>Поздравляю! Что вы мне тут пытаетесь доказать? Что умеете сами управлять зависимостями? Хахаха!
бери больше: "как управлять миром, не привлекая внимание санитаров"
ну, кстати, тоже из опыта работы с кодерами из бангалора-бангладеша: они часто любят понаписать какие-то свои контейнеры, аггрегаторы событий и всякое подобное, которое, естественно, не дружит ни с многопоточностью, не удаляется из памяти сборщиком мусора, но споровождается всё это тем самым : "теперь-то мы знаем, как оно работает"
Здравствуйте, VladD2, Вы писали:
МП>>использование элементов класса может быть неявным через IoC-фреймворк (сейчас понимаю лучше формулировать так) МП>>увидев в IDE 0 количество использований — придётся озираться, а не использует ли его IoC-фреймворк, а не удалить сходу VD>Это как бы проблема не IoC/DI, а использования базовых типов (интерфейсов и базовых классов) в проектировании. А она вызвана тем самым D из SOLID, т.е. dependency inversion principle, а не DI/IoC. DI лишь механизм для упрощения dependency inversion principle. Ты путаешь причину со следствием.
Это именно проблема IoC/DI, т.к. он может конфигурироваться из файла. При D. Inversion и без D. Injection у нас все равно как минимум
одно использование типа в коде будет (кто-то же должен его создать?). А при создание может конфигурироваться хрен знает где
и хрен знает как, и студия может об этом вообще ничего не знать. Вот и показывает, что тип нигде не используется, хотя
он явно прописан в конфиге как зависимость.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Sharov, Вы писали:
S>>Это именно проблема IoC/DI, т.к. он может конфигурироваться из файла. При D. Inversion и без D. Injection у нас все равно как минимум S>>одно использование типа в коде будет (кто-то же должен его создать?). А при создание может конфигурироваться хрен знает где S>>и хрен знает как, и студия может об этом вообще ничего не знать. Вот и показывает, что тип нигде не используется, хотя S>>он явно прописан в конфиге как зависимость.
VD>Что за "логика"? То что что-то там может — это не значит, что это является причиной. Ты создавая экземпляр можешь значительно больше чем любой фрэймворк. У нас вот проекте конфигурация из файла не используется в принципе. Вся конфигурация в коде.
А если не в коде, как у ТС?
VD>Если у тебя в коде интерфейс, ты автоматически получаешь большую неопределенность нежели при работе с конкретным типом, так как ты работаешь с абстракцией. Работу с абстракцией выбираешь ты сам. Если этот выбор сделан осознанно и грамотно, а не потому, что "все так делают", то ты преследовал какую-то цель. И вот эта вот цель подразумевает подмену типа в рантайме. Используя DI ты волен создавать не только экземпляры конкретных типов, но и даже делать их все sealed, чтобы гарантировать, что они не могут быть подменены в рантайме.
VD>Так что не говори чушь. Причина именно dependency inversion. Ты путаешь причину со следствием.
Речь о том, что ТС поудалял кучу классов (или собирался это сделать), т.к. R# показал, что они нигде не используются.
Потом выяснилось, что они используются, только слишком неявно, в конфиге, т.к. работают через IOC контейнер. С
этого данная тема по сути и началась. Так что причина именно Injection, а не Inversion.
Здравствуйте, Sharov, Вы писали:
S>А если не в коде, как у ТС?
У ТС DI не используется по причини не любви к ним. А все остальное попытка подобрать аргументы к исходной установке.
S>Речь о том, что ТС поудалял кучу классов (или собирался это сделать), т.к. R# показал, что они нигде не используются. S>Потом выяснилось, что они используются, только слишком неявно, в конфиге, т.к. работают через IOC контейнер. С S>этого данная тема по сути и началась. Так что причина именно Injection, а не Inversion.
Во-первых, это слишком вольная интерпретация сказанного.
Во-вторых, этого можно добиться и создавая тип скажем через активатор.
В-третьих, люди использующие DI должны понимать что происходит и производить поиск другими методами. В нашем случае, например, ищутся вызовы "...Register<НужныйТип>(...". Сам DI не знает что подсунуть вместо интерфейса. Стало быть без регистрации не обойтись. Ну, а если это конкретный sealed тип и в регистрации он не замечен, то он тупо создается через DI.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>>Так что не говори чушь. Причина именно dependency inversion. Ты путаешь причину со следствием.
S>Речь о том, что ТС поудалял кучу классов (или собирался это сделать), т.к. R# показал, что они нигде не используются. S>Потом выяснилось, что они используются, только слишком неявно, в конфиге, т.к. работают через IOC контейнер. С S>этого данная тема по сути и началась. Так что причина именно Injection, а не Inversion.
ну так это элементарно лечится: code-based configuration — "это то, что доктор прописал",
ну а уж если сам хочешь извращений, их тоже есть: и xml-based configuration, и convention over configuration...
Здравствуйте, SkyDance, Вы писали:
AA>>по мне так это философия. как только нужно расширить поведение объекта, так сразу возникает необходимость интерфейса и следовательно фабрики.
SD>Не вижу связи. Фабрика — это лишь способ добавить turing-full код туда, где изначально его не было. А DI — это способ вернуть с уровня turing full на DSL (см. configuration complexity clock). И то, и другое — в runtime, то есть поздно и не особо безопасно.
Считаю тема ради хайпа.
Лично мое мнение, выучи все подходы, а потом забудь все и пиши сообразуясь с ситуацией.
·>То добавить dao1 в Svc2 никаких дополнительных правок не потребует.
Разве, а конструктор? Впрочем, не важно.
·>Зато в Dao1 добавить svc1 у тебя просто не получится скомпилировать.
Не получится, да. А при использовании ioc-контейнера может и получиться (смотря как конфигурация описана).
·>Далее по тексту начинался новый параграф.. я не понял в чём "но".
Ну, перед этим по тексту ) Я по смыслу имел в виду — в "автоматическом" управлении зависимостями (контейнер) vs "статическом" есть свои плюсы и минусы, и возникновение ошибки в рантайме минус, конечно — но нужно учитывать, что "рантайм" тут не тот, который будет долго ждать своего часа.
В целом, для себя дискуссию по ioc-контейнерам считаю законченной, дополнительные выводы по ним сделал, и спасибо всем за мнения. С ними так или иначе приходится жить частенько (по различным причинам) — и ортодоксально отовсюду их выпилить не всегда возможно, да и не всегда оправданно. Однако, при неких удобствах они, за исключением некоторых специфичных применений (описанных в т.ч. VladD2) ухудшают качество дизайна, и это необходимо понимать. Это так, немного банальностей в качестве резюме для себя.
·>Да. Просто добавляешь и introduce parameter, IDE сама всё сделает.
Ну то такое, оттуда потом ноги у проверок на null и прочего растет.
·>Так это плохо, т.к. у тебя возникнут циклические зависимости между слоями приложения.
Именно, поэтому об этом и написал. Нужно учитывать такого рода вещи при проектировании конфигурации, дизайна. Как и обычно, впрочем.
·>По умолчанию у тебя один контекст со всеми зависимостями, глобальая переменная. Разделять контекст на части в контейнерах очень тяжело и то что ошибки стали рантайм — ещё сильнее бьёт.
Что именно подразумевается под "разделять контекст на части в контейнерах"?
·>В том то и дело, увидеть эти ошибки — нужно ждать часа. В статическом ошибки у тебя тут же подсвечиваются красным в IDE.
При первом запуске, да. То, что "подсвечиваются красным" — получше, конечно.
·>Единственное неоспоримое удобство которое в дискуссии было описано — если код пишешь в нотепаде, то нужно нажимать меньше кнопок. ·>Поэтому я считаю контейнеры пережитком прошлого, когда код писать было сложно.
Отчасти понимаю позицию, раз так, то пусть будет для вас так.
p> ·>По умолчанию у тебя один контекст со всеми зависимостями, глобальая переменная. Разделять контекст на части в контейнерах очень тяжело и то что ошибки стали рантайм — ещё сильнее бьёт.
p> смотря как конфигурация описана
Видимо я не понял это. А как какие варианты ты имел в иду?
p> Что именно подразумевается под "разделять контекст на части в контейнерах"? https://www.baeldung.com/spring-boot-context-hierarchy https://www.blackpepper.co.uk/blog/a-modular-architecture-with-spring-boot
По-моему это просто ужас. Вместо пары классов для модулей тут какие-то шаманства с аннотациями и хитрыми api.
p> ·>В том то и дело, увидеть эти ошибки — нужно ждать часа. В статическом ошибки у тебя тут же подсвечиваются красным в IDE. p> При первом запуске, да. То, что "подсвечиваются красным" — получше, конечно.
Нередко бывает, что первый запуск который выявит эти ошибки случается только после коммит-пуш-билд-деплой цикла и "час" тут не фигура речи.
·> (лишь за счёт того, что мы называем конфиги не кодом!) без каких-либо улучшений качества.
Ага, любимый трюк плохих программистов.
Тестировать лень?
Давайте добавим "конфиг" — переменную "фича включена".
И сделаем slow rollout. Если юзеры не жалуются, значит, все хорошо, пожалуются — выключим, исправим, снова slow rollout.
Добавим так с десяток фич, и выкатывать следующую — жуть и ужас, потому что что бы ты ни тронул, где-то сломается, а протестировать можно только в продакшн.
Здравствуйте, SkyDance, Вы писали:
AA>>Лично мое мнение, выучи все подходы, а потом забудь все и пиши сообразуясь с ситуацией.
SD>Выше по теме уже выразили мнение, с которым я особенно согласен: лучший код — тот, который сразу понятно что делает. А это, в свою очередь, такой код, где минимум уровней абстракции, через которых нужно продраться.
Согласен, меньше кода — проще код.
Простая логика.
Но все равно существуют разные мнения.
Например, Рич Хикки и Роберт Мартин. Первый за простоту, второй за архитектуру со старта, а это значит DI и прочие прелести, когда они еще не особо вроде и нужны.
Здравствуйте, SkyDance, Вы писали:
AA>>по мне так это философия. как только нужно расширить поведение объекта, так сразу возникает необходимость интерфейса и следовательно фабрики.
SD>Не вижу связи. Фабрика — это лишь способ добавить turing-full код туда, где изначально его не было. А DI — это способ вернуть с уровня turing full на DSL (см. configuration complexity clock). И то, и другое — в runtime, то есть поздно и не особо безопасно.
Как-то продивинуто . Зачем тут полноту по Тьюрингу поминать? Вся суть в том, что DI\IoC не предполагает
явно создания объектов программистам, но иногда это необходимо. Для этих целей фабрику и выдумали и инжектят
ее в конструкторах, чтобы иметь больше контроля над созданием объектов. Как я это понимаю. Т.е. new вызывать нельзя,
но когда сильно надо используют фабрику.
Здравствуйте, VladD2, Вы писали:
VD>Так за все приходится платить. Разбираться в коде созданном на базе большинства DI действительно сложнее. По уму нужно делать статический DI, который резолвил бы зависимости во время компиляции. Это сняло бы много проблем. Но современные мэйстрим-языки на это не особо рассчитаны. Вот и появляются строковые конфиги. Ошибки при загрузке. Непонимание того почему тут подсунули некоторый тип, а не иной. И т.п.
Что значит "статический DI" ? Он из коробки такой, а ломает его IoC, который вообще может из файла конфигурироваться.
Здравствуйте, VladD2, Вы писали:
VD>Из коробки его нет. Все DI — это IoC. Обратное не верно. Не пори чушь.
Это почему не верно?
VD>Причем обязанности поддерживать конфигурацию на текстовых файлах у них нет. Это твои домыслы. Это не более чем особенность реализации.
А если именно конфигурационные файлы используются для настройки графа?
VD>По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей и автоматическое создание объектов в последовательности зависимостей. Так вот этот процесс можно осуществлять как в рантайме, так и во время компиляции. Последнее дает больше контроля и избавляет от кучи проблем.
Все именно так, согласен. Вот только этих графов может быть несколько, а не один. Проверить можно, наверное, только
если sealed классы используются, иначе это будет трудновато -- перебирать все возможные варианты, что может быть
долго.
Здравствуйте, Министр Промышленности, Вы писали:
МП>Они затрудняют распутывание кода,
Да.
МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом)
Да.
МП>и это не перекрывается гибкостью подстановки mock-объектов
Моки тут вообще побоку.
МП>кто-то может популярно расписать преимущества либо природу явления популярности?
Главное преимущество — открытый интерфейс расширения. Т.е. если у тебя:
Расширяемая без перекомпиляции система
Поверхность расширения большая и постоянно существенно изменяющаяся
Есть потребность в том, чтобы кто то со стороны мог сделать не просто свое расширение в существующий extension point, но и добавить собственный extention point
то при попытке реализовать эти требования более менее качественно ты все равно придешь к чему то вроде DI контейнера.
Ну а популярность среди ориентированной на модность и молодежность аудитории объясняется просто — DI позволяет быстрее лопатить код, а морочится с его поддержкой в дальнейшем — это уже не модно и не молодежно.
Здравствуйте, gandjustas, Вы писали:
МП>>но обнаруживаю ярых адептов этого всего. МП>>уже и в вакансиях суют такое требование G>Уже? Доброе утро. Погугли что означает D в SOLID
Dependency Inversion. Ты бы сам погуглил сперва, а то вышло как обычно.
Здравствуйте, СвободуАнжелеДевис, Вы писали:
САД>но вот тебе пример из недавнего с нашего проекта. САД>есть сервис, пусть будет MyService, который делает достаточно долгий запрос за статическими данными. САД>есть интерфейс, понятное дело, IMyService. САД>этот сервис используется в большом количестве мест проекта. через интеферйс, понятное дело, не напрямую создавая инстанс. САД>запустив нагрузочное тестирование, мы поняли, что мы упираемся в производительность этого сервиса, и если бы мы закешировали данные, получили бы приличный прирост производительности. САД>дальше, мы создали над MyService обычный декоратор, аля MyServiceWithCache, который так же реализует IMyService интерфейс. всё что там нужно было, поменять регистрацию в DI в одном месте. САД>дальше, везде подхватилась новая реализаций, реализация нашего декоратора. приложение взлетело.
Круто, но все тоже самое можно было бы сделать без DI, заменив его на банальную фабрику.
Здравствуйте, VladD2, Вы писали:
VD>Иногда DI заставляет вводить лишние абстракции. Скажем вводить интерфейсы там где у них никогда не будет более одной реализации.
DI контейнеры такого совсем не требуют.
VD> Код завязанный на коркретный DI хрен перенесешь в другой проект.
Ну это если DI контейнер криво спроектирован (да, большая часть как раз такие, но, к счастью, не все). Если же он спроектирован нормально (например родной МСовский), то переносить надо только код регистрации компонентов, а он обычно сравнительно небольшой и тривиальный по структуре.
VD> Иногда DI приводит к тому, что хрен поймешь, что за тип к тебе в реальности приехал и где та магия, которая к этому привела.
Не иногда, а очень часто.
VD>Многие проблемы DI происходят от того, что DI-фрэймворки динамические, создают граф зависимостей в рантайме. Если бы зависимости разрешались бы статически
Здравствуйте, Философ, Вы писали:
Ф>Нет не принимается: абстрактная фабрика точно так же уменьшает связность и развязывает зависимости. Ключ здесь — использование интерфейсов вместо конкретных классов.
Dependency Injection вовсе не обязует использовать интерфейсы.
Здравствуйте, Sharov, Вы писали:
S>·>Мде уж. Это у тебя полная путаница. Основная фича DI — это отделить конструирование компонентов от их использования. S>Без графа зависимостей этого не сделаешь? Но да, это не главная фича, а компонент этой фичи.
Не знаю что ты конкретно под этим подразумеваешь, но вот это
и есть граф зависимостей: dbContext зависит от dbOptions, mainService зависит от dbContext и т.п.
Притом, если использовать исключительно Constructor Injection, то у тебя гарантированно получится DAG — а это круто с т.з. красивости архитектуры.
Только он нарисован вручную, в момент написания кода, а не автоматически сгенерирован в runtime.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Ночной Смотрящий, Вы писали:
НС> Тё>DI это довольно старый паттерн. НС> Паттерн это Dependency Inversion. А Dependency Injection это несколько иное.
Ты по-моему тоже попутал Dependency Inversion это Principle, а не pattern.
Здравствуйте, SkyDance, Вы писали:
S>>Как-то продивинуто . Зачем тут полноту по Тьюрингу поминать? Вся суть в том, что DI\IoC не предполагает S>>явно создания объектов программистам, но иногда это необходимо.
SD>Создать объекты можно безо всяких там DI/IoС. SD>Как уже было замечено выше, "контейнер" (или composition root) — это способ назвать код "конфигурацией" и вынести его туда, где тестировать его становится сложно, а значит, (барабаны), не нужно.
SD>Был тут уже выше пример про то, что хочется назвать определенную сборку "продакшном", и под этим соусом избежать тестирования.
Это все хорошо, но только для небольших проектов, если проект сложный, то и способ сборки частей будет чуть хитрее чем простая передача экземпляра в конструктор.
Там могут быть существенны и скорость компиляции и возможность замены модулей...
AA>Там могут быть существенны и скорость компиляции и возможность замены модулей...
Вот над этим и работайте. Ускоряйте компиляцию, современные билд-системы очень даже хорошо с этим справляются. Даже безо всякой распределенной магии, просто путем сборки только тех зависимостей, которые изменились.
А если у вас есть такая банальщина как key-value storage, вы просто можете готовые build artifacts туда складывать, как mapping sha(SourceFile) -> BuildArtifact (примерно так работают bazel/buck/...).
Почему именно там?
M> где куча разнообразной логики которая имеет свойство часто меняться, это приводит к тому что меняются сигнатуры конструкторов, их зависимости и т.д.
И? Если сигнатуры поменялись, тебе их все равно где то придется поправить, хоть с DI, хоть без.
Здравствуйте, IT, Вы писали:
IT>Я такое при возможности выкорчёвываю нещадно.
Только в случае использования asp.net core, и даже просто голого GenericHost1 уже все, выкорчевать ничего не выйдет, оно там прибито намертво гвоздями.
Здравствуйте, VladD2, Вы писали:
VD>По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей и автоматическое создание объектов в последовательности зависимостей.
Нет. Основная фича DI контейнеров — управление созданием и уничтоженим экземпляров сервисов, и рантайм биндинг этих экземпляров к потребителям (слово injection в названии четко и недвусмысленно определяет что такое DI). Что то там рассчитывать на графе зависимостей — совершенно необязательная фича, которой может просто не быть.
Здравствуйте, Sharov, Вы писали:
S>·>Мде уж. Это у тебя полная путаница. Основная фича DI — это отделить конструирование компонентов от их использования. S>Без графа зависимостей этого не сделаешь?
Что именно не сделать без явного графа зависимостей?
Здравствуйте, Ночной Смотрящий, Вы писали:
IT>>Я такое при возможности выкорчёвываю нещадно. НС>Только в случае использования asp.net core, и даже просто голого GenericHost1 уже все, выкорчевать ничего не выйдет, оно там прибито намертво гвоздями.
Мой самый первый пост был как раз об этом.
Если нам не помогут, то мы тоже никого не пощадим.
и есть граф зависимостей: dbContext зависит от dbOptions, mainService зависит от dbContext и т.п. ·>Притом, если использовать исключительно Constructor Injection, то у тебя гарантированно получится DAG — а это круто с т.з. красивости архитектуры. ·>Только он нарисован вручную, в момент написания кода, а не автоматически сгенерирован в runtime.
Я именно про DAG, созданный в runtime. Т.е. помимо кода это где-то хранится(отражается) в памяти.
Здравствуйте, SkyDance, Вы писали:
SD>Создать объекты можно безо всяких там DI/IoС. SD>Как уже было замечено выше, "контейнер" (или composition root) — это способ назвать код "конфигурацией" и вынести его туда, где тестировать его становится сложно, а значит, (барабаны), не нужно.
Не до конца понимаю почему это плохо? Я сейчас уже IaC (infrastructure as a code) во всю, чем плоха
конфигурация как код? Тестирование -- это все-таки организационный вопрос, а не принципиальный.
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>·>Мде уж. Это у тебя полная путаница. Основная фича DI — это отделить конструирование компонентов от их использования. S>>Без графа зависимостей этого не сделаешь? НС>Что именно не сделать без явного графа зависимостей?
Здравствуйте, Ночной Смотрящий, Вы писали:
VD>>По ходу ты не понимаешь того что лежит за DI. Основная фича DI — это расчет графа зависимостей и автоматическое создание объектов в последовательности зависимостей. НС>Нет. Основная фича DI контейнеров — управление созданием и уничтоженим экземпляров сервисов, и рантайм биндинг этих экземпляров к потребителям (слово injection в названии четко и недвусмысленно определяет что такое DI). Что то там рассчитывать на графе зависимостей — совершенно необязательная фича, которой может просто не быть.
А как узнать тогда кто и от чего зависит, что сделать инъекцию? Т.е. каков порядок создания объектов тогда,
без графа?
и есть граф зависимостей: dbContext зависит от dbOptions, mainService зависит от dbContext и т.п. S>·>Притом, если использовать исключительно Constructor Injection, то у тебя гарантированно получится DAG — а это круто с т.з. красивости архитектуры. S>·>Только он нарисован вручную, в момент написания кода, а не автоматически сгенерирован в runtime. S>Я именно про DAG, созданный в runtime. Т.е. помимо кода это где-то хранится(отражается) в памяти.
Всё равно я не понял вопрос "Без графа зависимостей этого не сделаешь?".
Если чё, объекты в памяти, которые ссылаются друг на друга и есть граф создаваемый и модифицируемый в runtime... DI тут непричём.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
AA>Это все хорошо, но только для небольших проектов, если проект сложный, то и способ сборки частей будет чуть хитрее чем простая передача экземпляра в конструктор.
В таком варианте — "способ сборки частей будет чуть хитрее" — тем более нельзя оставлять это на совести нетестируемого XML-файла в "конфигурации проекта".
Здравствуйте, Sharov, Вы писали:
НС>>DI это и есть проект с сервис-локаторами и синглтонами, просто в несколько более запутанном виде. S>Все-таки не совсем.
Все таки совсем. Внутри любого контейнера — service locator
S>При DI мы получаем только нужны нам типы, про контейнер нам знать вообще не надо.
Тем не менее внутри любого известного мне контейнера ссылки на service locator присутвуют в публичном API.
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>Все-таки не совсем. НС>Все таки совсем. Внутри любого контейнера — service locator
SL -- это подход, паттерн, когда программист самостоятельно вызывает Resolve для соотв. типа,
т.е. у нас что-то вроде
public Constructor(Container ioc)
{
var type1 = ioc.Resolve<IType1>();
....
}
В то время как DI
public Constructor(IType1 type1)
{
}
Т.е. SL реализуется с помощью контейнера, и как контейнер может реализовать SL с помощью... контейнера ?
S>>При DI мы получаем только нужны нам типы, про контейнер нам знать вообще не надо. НС>Тем не менее внутри любого известного мне контейнера ссылки на service locator присутвуют в публичном API.
Здравствуйте, ·, Вы писали:
S>>·>Только он нарисован вручную, в момент написания кода, а не автоматически сгенерирован в runtime. S>>Я именно про DAG, созданный в runtime. Т.е. помимо кода это где-то хранится(отражается) в памяти. ·>Всё равно я не понял вопрос "Без графа зависимостей этого не сделаешь?". ·>Если чё, объекты в памяти, которые ссылаются друг на друга и есть граф создаваемый и модифицируемый в runtime... DI тут непричём.
Все так, конечно, только вопрос кто и как формирует этот граф -- программист через new (не важно,
фабрика, билдер и проч. и проч.) или некий компонент, через рефлексию, например. https://blog.ploeh.dk/2011/07/28/CompositionRoot/
Здравствуйте, SkyDance, Вы писали:
AA>>Это все хорошо, но только для небольших проектов, если проект сложный, то и способ сборки частей будет чуть хитрее чем простая передача экземпляра в конструктор.
SD>В таком варианте — "способ сборки частей будет чуть хитрее" — тем более нельзя оставлять это на совести нетестируемого XML-файла в "конфигурации проекта".\
Да не спорю я что явное лучше не явного, но иногда динамическое связывание просто необходимо.
Вот например плагины пользователя как создать явно? когда имеется возможность загрузки/выгрузки?
Есть хорошая штука — проперти-бейс тестирование, интеграционные тесты. в учебно-полевых условиях тестируются функции.
такой эксперимент доказывающий, что софт работает корректно.
Здравствуйте, Poopy Joe, Вы писали:
PJ>Здравствуйте, Somescout, Вы писали:
S>>Банальная лень. Допустим мне в глубинах компонентов понадобился доступ к базе (оставим пока в сторонке вопросы архитектуры), как мне получить контекст базы без DI? PJ>Отличное описание. DI это костыль, когда лень думать об архитектуре, но не лень потом все это разгребать и отлаживать.
Какая это лень? Это способ уменьшить кол-во строк кода, один из базовых принципов KISS & DRY.
Представьте себе java 10-15 лет назад, не знаю как сейчас. Куча бесполезного кода чтобы создать объект.
И тут бах, DI, AOP и понеслась. Кол-во кода резко уменьшилось, а с ним возможность совершить опечатку, стало проще читать этот код.
Здравствуйте, Sharov, Вы писали:
S>>>При DI мы получаем только нужны нам типы, про контейнер нам знать вообще не надо. НС>>Тем не менее внутри любого известного мне контейнера ссылки на service locator присутвуют в публичном API.
S>Можно пример, ссылку?
Вот же(C# ):
using Microsoft.Extensions.DependencyInjection;
......
var scope = serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetService<IService>();
Здравствуйте, Sharov, Вы писали:
S>SL -- это подход, паттерн,
Да. И вокруг него построены все DI клнтейнеры.
S> когда программист самостоятельно вызывает Resolve для соотв. типа,
Нет.
The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task.
Википедия. Как видишь, ни слова про "самостоятельно вызывает Resolve". И прекрасно подходит для описания DI контейнеров.
S>Т.е. SL реализуется с помощью контейнера,
Контейнер и есть — service locator.
S>Можно пример, ссылку?
public static IServiceCollection AddSingleton<TService,TImplementation> (this IServiceCollection services, Func<IServiceProvider,TImplementation> implementationFactory) where TService : class where TImplementation : class, TService;
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Нет. НС>
НС>The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task.
НС>Википедия. Как видишь, ни слова про "самостоятельно вызывает Resolve". И прекрасно подходит для описания DI контейнеров.
Да. В википедии умеют в D. Inversion , поэтому они абстрагировались от "самостоятельно вызывает Resolve", потому что
это детали, а вместо написали:
which on request returns the information necessary to perform a certain task
НС>Контейнер и есть — service locator.
Да, я это в примере выше с двумя конструкторами отчетливо показал. Т.е. когда у нас D. Injection мы вообщем-то
вообще можем не знать про IoC контейнер (где-то там, а может и вообще нету). А когда у нас SL, то мы завязываемся
на контейнер. Короче, разница в том, как использовать контейнер -- явно SL, неявно -- DI.
S>>Можно пример, ссылку? НС>
НС>public static IServiceCollection AddSingleton<TService,TImplementation> (this IServiceCollection services, Func<IServiceProvider,TImplementation> implementationFactory) where TService : class where TImplementation : class, TService;
И о чем мне данная сигнатура должна сказать? Когда мне понадобиться тип TService он будет создан
Func<IServiceProvider,TImplementation> implementationFactory. Хорошо, и?
S>>Все так, конечно, только вопрос кто и как формирует этот граф -- программист через new (не важно, S>>фабрика, билдер и проч. и проч.) или некий компонент, через рефлексию, например. НС>Этот вопрос никак не вляет на суть того что такое DI контейнер, и является деталью его реализации.
Я не очень понимаю, что получится тогда без этой детали . Такая себе, краеугольная деталь.
Здравствуйте, Sharov, Вы писали:
S>Да. В википедии умеют в D. Inversion , поэтому они абстрагировались от "самостоятельно вызывает Resolve", потому что S>это детали
Нет, не потому что это детали, а потому что это не является признаком паттерна. Там правильное и точное определение, не надо к нему докапываться.
НС>>Контейнер и есть — service locator. S>Да, я это в примере выше с двумя конструкторами отчетливо показал. Т.е. когда у нас D. Injection мы вообщем-то S>вообще можем не знать про IoC контейнер
Мы сейчас обсуждаем не голый принцип, а контейнеры, я каждый раз явно про это писал. И при обсуждении контейнера мы не можемабстрагироваться от него.
S>(где-то там, а может и вообще нету). А когда у нас SL, то мы завязываемся S>на контейнер.
В случе дотнета — не совсем. Большая часть нормальных фреймворков опираются на System.IServiceProvider, который присутствует в дотнете аккурат с версии 1.0. А поделия боа-бла архитектов, изобретающих свой ISP не вижу смысла обсуждать — если руки из жопы, то руки из жопы.
S> Короче, разница в том, как использовать контейнер -- явно SL, неявно -- DI.
Ну смотри:
class MyService
{
MyService(IServiceProvider provider)
{
var service = provider.GetService(ResolveCustomServiceType(...));
...
}
}
Тут как, явно или неявно?
S>И о чем мне данная сигнатура должна сказать?
О том что разговаривать о штатном DI контейнере дотнета в отрыве от service locator нельзя, это часть его публичного API.
S>Когда мне понадобиться тип TService он будет создан S>Func<IServiceProvider,TImplementation> implementationFactory. Хорошо, и?
И IServiceProvider в сигнатуре не видишь? Я же жерным специально выделил.
Здравствуйте, Sharov, Вы писали:
S>Я не очень понимаю, что получится тогда без этой детали .
Нормально получится. Есть легковесные контейнеры без этой фичи.Разница в основном в поведении на негативных сценариях.
S> Такая себе, краеугольная деталь.
Почему краеугольная? Еще раз спрашиваю — что за ключевой функционал DI контейнеров нельзя реализовать без явного построения и анализа графа зависимостей?
Здравствуйте, Ночной Смотрящий, Вы писали:
S>> Короче, разница в том, как использовать контейнер -- явно SL, неявно -- DI. НС>Ну смотри: НС>
НС>class MyService
НС>{
НС> MyService(IServiceProvider provider)
НС> {
НС> var service = provider.GetService(ResolveCustomServiceType(...));
НС> ...
НС> }
НС>}
НС>
НС>Тут как, явно или неявно?
Явная, конечно. Была бы неявная, если бы параметром конструктора был CustomServiceType.
S>>И о чем мне данная сигнатура должна сказать? НС>О том что разговаривать о штатном DI контейнере дотнета в отрыве от service locator нельзя, это часть его публичного API.
Разумеется нельзя, если это по сути один и тот же объект. Вся разница в его употреблении.
В примере выше у нас контейнер как SL, если убрать зависимость от контейнера в параметре -- у нас DI.
Которая тем же контейнером, но без нашего участия, отменно сконфигурирует (разрешит все зависимости)
для конструируемого объекта.
Здравствуйте, Sharov, Вы писали:
S>Явная, конечно.
Почему? Типов DI контейнера в коде нет.
S> Была бы неявная, если бы параметром конструктора был CustomServiceType.
А если CustomServiceType неизвестен на этапе компиляции?
S>>>И о чем мне данная сигнатура должна сказать? НС>>О том что разговаривать о штатном DI контейнере дотнета в отрыве от service locator нельзя, это часть его публичного API. S>Разумеется нельзя, если это по сути один и тот же объект.
Бинго! Теперь возвращаемся в начало. Как в таком разе IncrementTop умудляется противопоставлять service locator и DI контейнер?
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>Явная, конечно. НС>Почему? Типов DI контейнера в коде нет.
Типов нет, интерфейс есть.
S>> Была бы неявная, если бы параметром конструктора был CustomServiceType. НС>А если CustomServiceType неизвестен на этапе компиляции?
Значит есть его интерфейс, иначе в коде выше с SL тоже немного смысла.
НС>Бинго! Теперь возвращаемся в начало. Как в таком разе IncrementTop умудляется противопоставлять service locator и DI контейнер?
По способу использования, очевидно -- это одна и та же сущность, которую по разному можно использовать.
S>> Такая себе, краеугольная деталь. НС>Почему краеугольная? Еще раз спрашиваю — что за ключевой функционал DI контейнеров нельзя реализовать без явного построения и анализа графа зависимостей?
У меня в коде операторов new практически нету, разве в Composition Root, как разрешать зависимости конструкторов?
С чего начать.
Здравствуйте, Sharov, Вы писали:
S>>>Явная, конечно. НС>>Почему? Типов DI контейнера в коде нет. S>Типов нет, интерфейс есть.
Интерейс это тоже тип. Поэтому интерфейсов тоже нет.
S>>> Была бы неявная, если бы параметром конструктора был CustomServiceType. НС>>А если CustomServiceType неизвестен на этапе компиляции? S>Значит есть его интерфейс
Нету. Есть только базовый интерфейс.
S>, иначе в коде выше с SL тоже немного смысла.
Достаточно там смысла. Конкретно в контейнере МС нет возможности зарегистрировать две реализации одного интерфейса одновременно (есть AddEnumerable, но там тоже свои ограничения). В таких ситуациях в контейнере регистрируется именно реализация, а интерфейс внутрь контейнера не лезет. И код подобный приведенному внутри библиотек вполне себе встречается.
НС>>Бинго! Теперь возвращаемся в начало. Как в таком разе IncrementTop умудляется противопоставлять service locator и DI контейнер? S>По способу использования, очевидно
Нет, не очевидно.
S> -- это одна и та же сущность, которую по разному можно использовать.
Пошли по кругу. Еще раз — DI контейнер это частный случай service locator, он строго соответствует определению этого термина, без оговорок. Поэтому противопоставлять SL и DI контейнер ни в каком контексте нельзя, это логически некорректно.
Здравствуйте, Sharov, Вы писали:
S>У меня в коде операторов new практически нету,
Как это связано с графом зависимостей?
S> разве в Composition Root, как разрешать зависимости конструкторов?
Зачем для этого граф? DI просто заменяет явный вызов ресолва сервиса на неявный, ориентируясь на сигнатуру ктора, методов или набор свойств. Каким боком тут нужен граф зависимостей? Ты этот вопрос уже три раза проигнорировал. Если нет ответа — так и скажи.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Интерейс это тоже тип. Поэтому интерфейсов тоже нет.
Тогда это не скомпилируется. Иначе хотелось бы увидеть пример.
S>>Значит есть его интерфейс НС>Нету. Есть только базовый интерфейс.
А интерфейс, как мы знаем, тоже тип. И зачем нам зависить от лишнего типа?
S>>, иначе в коде выше с SL тоже немного смысла. НС>Достаточно там смысла. Конкретно в контейнере МС нет возможности зарегистрировать две реализации одного интерфейса одновременно (есть AddEnumerable, но там тоже свои ограничения). В таких ситуациях в контейнере регистрируется именно реализация, а интерфейс внутрь контейнера не лезет. И код подобный приведенному внутри библиотек вполне себе встречается.
Возможно, контейнеры разные бывают.
НС>Нет, не очевидно.
Очень жаль
S>> -- это одна и та же сущность, которую по разному можно использовать. НС>Пошли по кругу. Еще раз — DI контейнер это частный случай service locator, он строго соответствует определению этого термина, без оговорок. Поэтому противопоставлять SL и DI контейнер ни в каком контексте нельзя, это логически некорректно.
Можно какой-нибудь содержательный интерент источник, где бы это утверждалось или обосновывалось? Мне
они видятся довольно параллельными подходами. Фундаментально параллельными, что аж SL нонче считаю антипаттерном.
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>У меня в коде операторов new практически нету, НС>Как это связано с графом зависимостей?
У меня нету в коде операторов new, но объекты-то надо как-то создавать? При старте каким-либо образом
формирую граф зависимостей, т.е. например банальный словарь -- [тип, типы необходимые для его создания].
Все это можно делать и на ходу, но на сколько понимаю, так быстрее и проще будет понять, что что-то не то...
Чем ждать неопределенное время.
Здравствуйте, Sharov, Вы писали:
НС>>Интерейс это тоже тип. Поэтому интерфейсов тоже нет. S>Тогда это не скомпилируется.
Скомпилируется.
S>Иначе хотелось бы увидеть пример.
Я тебе привел пример, что тебе в нем не нравится?
S>>>Значит есть его интерфейс НС>>Нету. Есть только базовый интерфейс. S>А интерфейс, как мы знаем, тоже тип.
Но его нельзя использовать для injection.
S> И зачем нам зависить от лишнего типа?
Он не лишний.
S>>> -- это одна и та же сущность, которую по разному можно использовать. НС>>Пошли по кругу. Еще раз — DI контейнер это частный случай service locator, он строго соответствует определению этого термина, без оговорок. Поэтому противопоставлять SL и DI контейнер ни в каком контексте нельзя, это логически некорректно. S>Можно какой-нибудь содержательный интерент источник, где бы это утверждалось или обосновывалось?
Определение из википедии я тебе привел. Если тебе оно не нравится — это твои проблемы, оно общепринятое.
S> Мне они видятся довольно параллельными подходами. Фундаментально параллельными, что аж SL нонче считаю антипаттерном.
Здравствуйте, Sharov, Вы писали:
S>>>У меня в коде операторов new практически нету, НС>>Как это связано с графом зависимостей? S>У меня нету в коде операторов new,
Как это связано с графом зависимостей?
S> но объекты-то надо как-то создавать? При старте каким-либо образом S>формирую граф зависимостей
Зачем?
S>, т.е. например банальный словарь -- [тип, типы необходимые для его создания].
Банальный словарь это не граф зависимостей.
S>Все это можно делать и на ходу, но на сколько понимаю, так быстрее и проще будет понять, что что-то не то... S>Чем ждать неопределенное время.
Вот вроде все слова понятны, а смысл фразы теряется.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Скомпилируется. S>>Иначе хотелось бы увидеть пример. НС>Я тебе привел пример, что тебе в нем не нравится?
Вот этот?
class MyService
{
MyService(IServiceProvider provider)
{
var service = provider.GetService(ResolveCustomServiceType(...));
...
}
}
Хотелось бы его увидеть до конца, самое интересное скрыто за ...
НС>Он не лишний.
Для DI он не нужный.
НС>>>Пошли по кругу. Еще раз — DI контейнер это частный случай service locator, он строго соответствует определению этого термина, без оговорок. Поэтому противопоставлять SL и DI контейнер ни в каком контексте нельзя, это логически некорректно. S>>Можно какой-нибудь содержательный интерент источник, где бы это утверждалось или обосновывалось?
НС>Определение из википедии я тебе привел. Если тебе оно не нравится — это твои проблемы, оно общепринятое.
Меня этот тезис интересует "DI контейнер это частный случай service locator", он мне кажется не совсем верным.
Если это частный случай антипаттерна, то как же тогда он может считаться лучше? Не анти-паттерном.
S>class MyService
S>{
S> MyService(IServiceProvider provider)
S> {
S> var service = provider.GetService(ResolveCustomServiceType(...));
S> ...
S> }
S>}
S>
S>Хотелось бы его увидеть до конца, самое интересное скрыто за ...
Ничего интересного там не скрыто, все что имеет отношение к делу там есть. Если что то непонятно — спрашивай.
НС>>Он не лишний. S>Для DI он не нужный.
Он нужен для реализации требований. А что там нужно для DI — плевать.
НС>>Определение из википедии я тебе привел. Если тебе оно не нравится — это твои проблемы, оно общепринятое. S>Меня этот тезис интересует "DI контейнер это частный случай service locator", он мне кажется не совсем верным.
Это не важно что тебе кажется. Определение из википедии говорит что он верный на 100%. Если не нравится то определение — найди другое из заслуживающего доверия источника. А фразы "мне видится" и "мне кажется" говорят только о проблемах с твоим пониманием термина, которые не вижу смысла обсуждать.
S>Если это частный случай антипаттерна, то как же тогда он может считаться лучше? Не анти-паттерном.
Потому что все эти рассказы про антипаттерн — вкусовщина отдельных личностей, а не истина в последней инстанции. В википедии на эту тему тоже сказано, если что:
Proponents of the pattern say the approach simplifies component-based applications where all dependencies are cleanly listed at the beginning of the whole application design, consequently making traditional dependency injection a more complex way of connecting objects. Critics of the pattern argue that it is an anti-pattern which obscures dependencies and makes software harder to test.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Ничего интересного там не скрыто, все что имеет отношение к делу там есть. Если что то непонятно — спрашивай.
Да, непонятно, что там за ... скрыто. Можно увидеть полный пример?
НС>Потому что все эти рассказы про антипаттерн — вкусовщина отдельных личностей, а не истина в последней инстанции. В википедии на эту тему тоже сказано, если что: НС>
НС>Proponents of the pattern say the approach simplifies component-based applications where all dependencies are cleanly listed at the beginning of the whole application design, consequently making traditional dependency injection a more complex way of connecting objects. Critics of the pattern argue that it is an anti-pattern which obscures dependencies and makes software harder to test.
Замечательно, но про "DI как частный случай SL" где прочитать?
Здравствуйте, Ночной Смотрящий, Вы писали:
S>>Замечательно, но про "DI как частный случай SL" где прочитать? НС>Голову включить и логику в ней. DI контейнер полностью подходит под определение SL. Значит DI является SL. При этом, очевидно, не каждый SL является DI контейнером. Отсюда следует, что DI является частным случаем SL.
Ты понятия попутал. "DI" != "DI container". Первое — это паттерн, второе — библиотека|фреймворк, который я бы более корректно называл "IoC Container", чтобы было меньше путаницы. SL это тоже паттерн, противоположный DI:
DI directly contrasts with the service locator pattern, which allows clients to know about the system they use to find dependencies.
Здравствуйте, ·, Вы писали:
·>Ты понятия попутал. "DI" != "DI container".
Ля, ну так и знал. Один раз из сотни забыл дописать контейнер, день было поправить, и тут же нашелся термин-наци. Контейнер там имелся в виду, везде — контейнер.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Ты понятия попутал. "DI" != "DI container". НС>Ля, ну так и знал. Один раз из сотни забыл дописать контейнер, день было поправить, и тут же нашелся термин-наци. Контейнер там имелся в виду, везде — контейнер.
Ясно, просто т.к. эти слова путают — ещё больше всё путается. Ок, вернёмся к предыдущему высказыванию, поправив слова
НС>DI контейнер полностью подходит под определение SL. Значит контейнер является SL. При этом, очевидно, не каждый SL является DI контейнером. Отсюда следует, что контейнер является частным случаем SL
По-моему тут тоже небольшая путаница из которого вырос этот ваш спор с Sharov. Контейнер сам по себе просто реестр, который предоставляет возможности SL, но и может делать DI. Т.е. формально, если взять себя в узду и никогда не использовать containter.Resolve<Type>() и аналоги, то можно использовать только DI-ную функциональность контейнера. Т.е. приложение может получиться только DI, хоть и с использованием контейнера. Вовсе не обязательно использовать контейнер как SL, или хотя бы свести к минимуму, используя только в случае интеграции с каким-то неподконтрольным 3rd party кодом.
С другой стороны, на практике мне не доводилось такого видеть. Даже в этом топике любители контейнеров так и сыпали везде этим SL, демонстрируюя вроде как "образцово-показательный" код. По-моему, это так потому что контейнеры позволяют писать плохой код без усилий.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
НС>>DI контейнер полностью подходит под определение SL. Значит контейнер является SL. При этом, очевидно, не каждый SL является DI контейнером. Отсюда следует, что контейнер является частным случаем SL ·>По-моему тут тоже небольшая путаница из которого вырос этот ваш спор с Sharov. Контейнер сам по себе просто реестр, который предоставляет возможности SL, но и может делать DI. Т.е. формально, если взять себя в узду и никогда не использовать containter.Resolve<Type>() и аналоги, то можно использовать только DI-ную функциональность контейнера.
Суть его от этого не меняется. Исторически все началось с хелперов, которые просто обеспечивали биндинг локатора и конструкторов/свойст, ну просто чтобы не писать бесконечные GetService/Resolve тоннами. Потом эти хелперы объединили с локатором в один фреймворк и обозвали модным словом injection. Вот и весь секрет Полишинеля. А потом уже бла-бла-архитекторы вывели из этого целую теорию, обозвали локатор антипаттерном а injection кошерным, и т.п.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>По-моему тут тоже небольшая путаница из которого вырос этот ваш спор с Sharov. Контейнер сам по себе просто реестр, который предоставляет возможности SL, но и может делать DI. Т.е. формально, если взять себя в узду и никогда не использовать containter.Resolve<Type>() и аналоги, то можно использовать только DI-ную функциональность контейнера. НС>Суть его от этого не меняется. Исторически все началось с хелперов, которые просто обеспечивали биндинг локатора и конструкторов/свойст, ну просто чтобы не писать бесконечные GetService/Resolve тоннами. Потом эти хелперы объединили с локатором в один фреймворк и обозвали модным словом injection. Вот и весь секрет Полишинеля. А потом уже бла-бла-архитекторы вывели из этого целую теорию, обозвали локатор антипаттерном а injection кошерным, и т.п.
Суть меняется в том смысле, что таки да, контейнер позволяет писать по старинке, но это не обязательно. Но если перестать пользоваться GetService/Resolve, выкинуть аннотации, property injection, использовать только constructor injection, то суть поменяется явно и качество кода улучшится.
Правда смысла использовать контейнер останется мало и следующий эволюционный шаг — выкинуть нафиг контейнер и получить compile-time checks.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Суть меняется в том смысле, что таки да, контейнер позволяет писать по старинке
Суть в том, что локатор как таковой никоим образом не детерминирует способ получения сервиса. Метод ли это Resolve или DI — локатор не перестает быть локатором.
Здравствуйте, IncremenTop, Вы писали:
НС>>DI это и есть проект с сервис-локаторами и синглтонами, просто в несколько более запутанном виде. IT>С чего бы?
Здравствуйте, SkyDance, Вы писали:
SD>Более удобным подходом было бы уметь генерировать граф зависимостей путем непосредственного анализа исходного кода.
Для чего более удобным?
SD> Однако там есть свои грабли, например, как понимать такую зависимость: SD> if (random(100) < 50) then add_dependency_on(AnotherComponent)
А как такая зависимость, по твоему, понимается в случае DI? Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн..
Здравствуйте, Sinclair, Вы писали:
S>А когда мы полагаемся на "уолшебный контейнер", то обнаружение кольцевой зависимости откладывается до рантайма. Поэтому такой код можно прекрасно закоммитить в репозиторий; и он даже может ездить в продакшне — до тех пор, пока кто-то не попытается активировать сервис доставки при помощи конфигурации.
Это все понятно, непонятно зачем контейнеру выстраивать явно граф зависимостей, тем более что он скорее всего будет неполным.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>·>Суть меняется в том смысле, что таки да, контейнер позволяет писать по старинке НС>Суть в том, что локатор как таковой никоим образом не детерминирует способ получения сервиса. Метод ли это Resolve или DI — локатор не перестает быть локатором.
Непонятно. SL подразумевает, что оттуда что-то можно получить, т.е. Resolve тебе найдёт что надо и выдаст. В то время DI же ничего не получает, ему всё дают. Как используя DI можно что-то получить? Это противоположные вещи.
Ты видимо путаешь Locator, Injector, контейнер.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Непонятно. SL подразумевает, что оттуда что-то можно получить, т.е. Resolve тебе найдёт что надо и выдаст. В то время DI же ничего не получает, ему всё дают.
Ты зарплату не получаешь, ее тебе дают? К чему эта софистика?
·> Как используя DI можно что-то получить?
Объявить параметр в конструкторе.
·> Это противоположные вещи.
Нет.
·>Ты видимо путаешь Locator, Injector, контейнер.
Здравствуйте, ·, Вы писали:
НС>>К чему эта софистика? ·>Это ключевое слово: Inversion of Control.
Нет такого ключевого слова в определении service locator. Ты упорно пытаешься смешать две разные вещи.
НС>>·> Как используя DI можно что-то получить? НС>>Объявить параметр в конструкторе. ·>Параметр объявляет тип, а инжектится инстанс.
И? При вызове Resolve указывается тип, а возвращает он инстанс.
·> И соотвественно в случае SL надо вызывать Resolve для того чтобы получить инстанс для данного типа.
Нет такого в определении service locator.
·>В динамических языках, где все типы "object"
Здравствуйте, ·, Вы писали: S>>Компилятор бъёт его по рукам, потому что получившийся DAG не сводится к дереву. ·>Чуть поправлю: "получившийся граф не сводится к DAG, т.к. содержит цикл". Деревья тут непричём.
Да, это я в запарке написал. Имел в виду именно то, что вы поправили.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Министр Промышленности, Вы писали:
МП>С моей профессиональной точки зрения DI фреймворки не нужны.
МП>Они затрудняют распутывание кода, МП>понижают гибкость автоматического рефакторинга (в частности ReSharper-ом) МП>и это не перекрывается гибкостью подстановки mock-объектов
МП>но обнаруживаю ярых адептов этого всего. МП>уже и в вакансиях суют такое требование
МП>кто-то может популярно расписать преимущества либо природу явления популярности? МП>(часть плюсов знаю и гипотезы-то я имею, но мнение всё равно такое)
А как же это?
Несмотря на то, что я использую Poor man's DI для исследования и объяснения механизма внедрения зависимостей,
я не рекомендую использовать его для профессионального использования.
Многие отличные DI-контейнеры доступны на .NET и все они являются бесплатными.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>Тебе тогда тот же вопрос. Какая обязательная фича DI контейнеров требует " расчет графа зависимостей"?
А ты попробуй представить упрощенный алгоритм создания экземпляра класса. Вот есть у тебя классы A, B и C. Они зависят один от другого. Скажем C от B и B от A. Как создать C? Сначала придется создать A и Б. Далее добауляем сюда регистпацию. И вот она проблема. Регистрация может произойти после создания регистрируемого типа. Если бы рассчет.зависимостей и регистрация происходили во время компиляции, этой проблемы не было бы, а все резолвы могла бы показать IDE.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>А ты попробуй представить упрощенный алгоритм создания экземпляра класса.
Влад, мне не надо его представлять, я сам парочку контейнеров написал, и код MS.DependencyInjection очень хорошо знаю.
VD> Вот есть у тебя классы A, B и C. Они зависят один от другого. Скажем C от B и B от A. Как создать C? Сначала придется создать A и Б.
И? Граф то зачем?
Я тебе больше скажу, в случае штатного контейнера дотнета его в принципе создать нельзя. Возьмем такой метод:
public static IServiceCollection AddSingleton (this IServiceCollection services, Type serviceType, Func<IServiceProvider,object> implementationFactory);
Как ты тут зависимости определишь? Будешь IL код анализировать?
VD> Далее добауляем сюда регистпацию. И вот она проблема. Регистрация может произойти после создания регистрируемого типа.
В случае штатной реализации дотнета — не может. Регистрируешь ты в IServiceCollection, а экземпляры создает IServiceProvider.
VD> Если бы рассчет.зависимостей и регистрация происходили во время компиляции, этой проблемы не было бы
Ее и так нет.
VD>, а все резолвы могла бы показать IDE.
Здравствуйте, Ночной Смотрящий, Вы писали:
НС>И? Граф то зачем?
Он сам собой получается. Зависимости формально описываются направленным ациклическим графом. Если есть циклы такие зависимости уже и создать то нельзя, точнее нужно специально вместе создавать.
НС>Как ты тут зависимости определишь? Будешь IL код анализировать?
Тут ты будешь этот код вручную описывать. Сначала создаешь сервис A, затем B, и затем C. И именно по этому такими DI пользоваться крайне неудобно и это прошлый век. Да они DI и не зовутся. Такое сервис-провайдерами называют. В VS от этого, например, давно ушли и перешли на MEF.
НС>В случае штатной реализации дотнета — не может. Регистрируешь ты в IServiceCollection, а экземпляры создает IServiceProvider.
Не "не можешь", а "полностью ложится на плечи программиста". Это вообще худшее из того что можно придумать. Таким подходом пользовались на заре компонентной архитектуры. В VS 2005, например. Тут управление зависимостями полностью ложится на тебя и, как следствие, то и дело прут ошибки когда одни сервис требует другой, а тот еще не загружен. Вот эти вот фабричные методы "implementationFactory" нужны для того, чтобы отложить создание на как можно более поздний срок. Но оно все равно стреляет. И в реальности оно выливается в Ад. Проверено на тех же студийных плагинах.
Именно по этому новые поколения стали управлять созданием зависимостей. Но и это взывает проблемы, если все делается динамически. А вот статический вариант решает большинство проблем, не создавая дополнительных. Но он сложен в реализации и поддержании по тому на сегодня практически не используется.
НС>Ее и так нет.
Есть и не в теории, а постоянно на практике вылезают. Вопрос лишь в объемах кода и числе разработчиков работающих над проектом.
НС>Что значит показать резолвы?
То и значит. 99% можно просчитывать статически. Ловить циклы. Показывать тот самый DAG зависимостей явно и ходить по нему. Математика вместо динамики.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Он сам собой получается.
О как. Только что рассчет графа был главной функцией, а теперь уже сам собой.
НС>>Как ты тут зависимости определишь? Будешь IL код анализировать? VD>Тут ты будешь этот код вручную описывать. Сначала создаешь сервис A, затем B, и затем C. И именно по этому такими DI пользоваться крайне неудобно и это прошлый век.
Это дотнетный родной, и он весьма свежий. И, разумеется, это не единственный вариант метода. Есть и другие, без фабрик. Но этот вариант наглядно демонстрирует что полного графа в любом случае построить нельзя. Что, при заявленной главной функции именно в построении графа выглядит крайне странно, не находишь?
VD> Да они DI и не зовутся.
Зовется оно Microsoft.DependencyInjection
НС>>В случае штатной реализации дотнета — не может. Регистрируешь ты в IServiceCollection, а экземпляры создает IServiceProvider. VD>Не "не можешь", а "полностью ложится на плечи программиста".
Именно не можешь. Нельзя ничего зарегистрировать после сборки контейнера.
VD>Вот эти вот фабричные методы "implementationFactory"
Ты опять читаешь по диагонали, теряя смысл.
VD>Именно по этому новые поколения стали управлять созданием зависимостей. Но и это взывает проблемы, если все делается динамически. А вот статический вариант
Статический вариант уже есть, это отказ от DI.
VD> решает большинство проблем, не создавая дополнительных.
Так какую проблему решает построение графа зависимостей?
НС>>Ее и так нет. VD>Есть и не в теории, а постоянно на практике вылезают. Вопрос лишь в объемах кода и числе разработчиков работающих над проектом.
Приведи пример вылезшей на практике необходимости построить граф зависимостей.
НС>>Что значит показать резолвы? VD>То и значит. 99% можно просчитывать статически.
Посчитать статически чего?
VD>Ловить циклы.
Как показывает практика, мни минимальном соблюдении несложных принципов дизайна циклы вообще не являются проблемой. Если единственное для чего нужен DI контейнер это ловить циклы, то он вообще не нужен.
Здравствуйте, VladD2, Вы писали:
VD>Подумай на досуге, как можно создавать сервисы не зная о их зависимостях и порядке создания.
Это просто. "Отказ от зависимости" и переход к "функциональной композиции".
Марк Земанн (автор DI) как раз сейчас проповедует "отказ" https://blog.ploeh.dk/2017/02/02/dependency-rejection/.
Правда для этого необходим соответствующий ЯП, наверно.
Еще в статье заметно, что частичное применение это то же DI только в ФП стиле.
В том же CL его вообще нет. Хотя штука может и удобная. Но коварная.
А вот композиция позволяет не думать о порядке, т.к. порядок задан явно.
Здравствуйте, varenikAA, Вы писали:
VD>>Подумай на досуге, как можно создавать сервисы не зная о их зависимостях и порядке создания. AA>Это просто. "Отказ от зависимости" и переход к "функциональной композиции".
Т.е. надо просто описать все зависимости и порядок создания.
AA>Марк Земанн (автор DI) как раз сейчас проповедует "отказ" https://blog.ploeh.dk/2017/02/02/dependency-rejection/. AA>Правда для этого необходим соответствующий ЯП, наверно.
Что-то я не понял смысл этой статьи... Это не про dependencies как таковые, а про разделение pure и impure-функций.
AA>Еще в статье заметно, что частичное применение это то же DI только в ФП стиле.
И собственно всё стоило говорить в теме DI в ФП стиле.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, varenikAA, Вы писали:
VD>>>Подумай на досуге, как можно создавать сервисы не зная о их зависимостях и порядке создания. AA>>Это просто. "Отказ от зависимости" и переход к "функциональной композиции". ·>Т.е. надо просто описать все зависимости и порядок создания.
Конечно нет. функциональной композиция, на мой взгляд, это абсолютное разделение зависимостей.
int add2 (int a) => a + 2;
int sum (a , Func<int,int> add) {
return a + add(a); // <= вот мы внедрли зависимость в sum, неважно объект это, интерфейс или функция
}
int add2 (int a) => a + 2;
int sum (a , int add) {
return a + add; // <= нет зависимости, чистая функция, легко использовать, меньше магии
}
var init = 1;
var result1 = add2(init);
var result2 = sum (init, result2);
Здравствуйте, varenikAA, Вы писали:
AA> ·>Т.е. надо просто описать все зависимости и порядок создания. AA> Конечно нет. функциональной композиция, на мой взгляд, это абсолютное разделение зависимостей.
Это какой-то игрушечный пример. В простых случаях можно делать просто. Вообще зачем это всё, пиши сразу void main() {print("result")} и не мучайся
AA> int add2 (int a) => a + 2;
В реальном коде это может быть некая сущность со смыслом предметной области.
Примени свою магию!
AA> return a + add; // <= нет зависимости, чистая функция, легко использовать, меньше магии
Вопрос не про меньше магии, а о смысле магии и где именно эта магия будет находиться.
Здравствуйте, varenikAA, Вы писали:
AA>Итог:
Мы всё это уже обсудили по нескольку раз. Мне лень повторяться ещё раз. Перечитай топик и отвечай на конкретные сообщения, если есть какие-то возражения.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, maxkar, Вы писали:
M>Никаким "расчетом графа зависимостей" здесь даже не пахнет. Есть что-то неявное, но до графа ему далеко. Причем так делает аж целый Spring — de-facto standard container в мире java!
Да почти все реальные контейнеры так делают. Поэтому забавно слышать сразу от нескольких человек про какие то рассчеты графов.
Здравствуйте, VladD2, Вы писали:
VD>Собственно проблема в том, что в таком подходе проблемы вявляются в рантайме, что плохо.
Влад, на практике нет таких проблем. Вот в текущем проекте за полтора года, не смотря на интенсивное использование DI, такой проблемы не было ни разу. Реальные проблемы DI расположены в совсем других местах, и это ни разу не "рассчет графа зависимостей".
SD>>Более удобным подходом было бы уметь генерировать граф зависимостей путем непосредственного анализа исходного кода. НС>Для чего более удобным?
Для тестирования, рефакторинга, и просто понимания — что от чего зависит, и где что сломается, если поменять вот этот компонент.
SD>> Однако там есть свои грабли, например, как понимать такую зависимость: SD>> if (random(100) < 50) then add_dependency_on(AnotherComponent) НС>А как такая зависимость, по твоему, понимается в случае DI? Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн..
Да мало ли что где клеймят. Если у тебя есть только одна сущность — "name" — а для работы с реальным миром нужно этот "name" во что-то развернуть, то хошь как хошь, а "Resolve" придется реализовать. Вопрос только в том, когда Resolve будет работать. Если во время компиляции — все чудесно, можно обложить тестами, можно научить IDE ходить по ссылками.
А если в рантайме, да где-то в каком-нибудь "DI container", да еще и на другом языке программирования (а то и вовсе на другой машине, — привет, DNS и аналогичные механизмы) — проще повеситься.
"Авторы DI", прямо скажем, умерли еще до того, как Марк начал писать.
Но удивительно, что у него заняло столько лет понять очевидную истину про implicit/explicit dependencies.
Прямо хоть самому начинать книжки писать. Нешто в самом деле очевидные вещи нужно разжевывать.
Здравствуйте, SkyDance, Вы писали:
SD>Для тестирования, рефакторинга, и просто понимания — что от чего зависит, и где что сломается, если поменять вот этот компонент.
Это должен делать DI контейнер?
НС>>А как такая зависимость, по твоему, понимается в случае DI? Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн.. SD>Да мало ли что где клеймят.
Предлагаешь отвечать без привязки к сообщению, на которое отвечаешь?
Как именно? Как DI container сможет протестировать какой-то вполне конкретный экземпляр того, что в итоге получитс?
НС>Предлагаешь отвечать без привязки к сообщению, на которое отвечаешь?
Не понимаю контекста этого комментария.
На настоящий момент мои знания про DI containers в основном ограничиваются всякими вариантами из мира Java, типа тех же Spring. И там я очень хорошо знаю, как за 15 минут настрочить такое, что тестировать будет куда сложнее, чем нафиг переписать без DI.
Здравствуйте, SkyDance, Вы писали:
НС>>Предлагаешь отвечать без привязки к сообщению, на которое отвечаешь? SD>Не понимаю контекста этого комментария.
А я не понимаю, как можно в обсуждении неправильности service locator и правильности DI игнорировать заявления о неправильности service locator.
SD>На настоящий момент мои знания про DI containers в основном ограничиваются всякими вариантами из мира Java, типа тех же Spring. И там я очень хорошо знаю, как за 15 минут настрочить такое, что тестировать будет куда сложнее, чем нафиг переписать без DI.
Это можно сказать про любую более менее сложную технологию.
Здравствуйте, VladD2, Вы писали:
VD>Поздравляю тебя. Ты сейчас описал метод построения графа зависимостей. Замени объекты на вершины, а "under construction" на флаги пометки обхода и вуаля. VD>Собственно проблема в том, что в таком подходе проблемы вявляются в рантайме, что плохо. Чем раньше известно о проблеме, тем проще и дешевле ее устранить.
Это да, но это происходит при запуске (должно, по крайней мере, происходить), поэтому если что, то
приложение сразу упадет.
Здравствуйте, Sharov, Вы писали:
S>Это да, но это происходит при запуске (должно, по крайней мере, происходить),
Запуск приложения — это тоже весьма не малый процесс, если речь идет о более менее большом приложении.
Кроме того этому предшествует сборка и деплоймент. В нашем случае, например, это очень дорогие поцессы. Продукт нельзя ставить локально. Только на вириален. А его сборка с тестами может занимать часы.
S>поэтому если что, то приложение сразу упадет.
Тоже не совсем так. К сожалению работа DI переплетена с обычным кодом инициализации. Для ускорения загрузки могут использовать потоки. В результате возникают баги, которые приходится анализировать часами, а то и днями.
Если же зависимости разруливались бы во время компиляции, ошибка выявлялась бы сразу.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
НС>А я не понимаю, как можно в обсуждении неправильности service locator и правильности DI игнорировать заявления о неправильности service locator.
Вы меня с кем-то спутали. Я не сторонник называть что-то правильным или неправильным. У каждого решения есть свои плюсы и минусы. Лично мне в DI (контейнерах) видится больше минусов, чем плюсов — я уже объяснял, что не вижу смысла код создания зависимостей на Java выносить в отдельные файлы (XML) и наворачивать дополнительные уровни абстракции для того, чтобы потом в итоге все равно создать Java объекты, все из того же реестра.
НС> Это можно сказать про любую более менее сложную технологию.
Кстати да, сложность — это не достоинство. Это недостаток. Сложность ограничивает нашу скорость движения вперед. И если эту сложность не получается хорошо скрыть за правильно подобраной абстрацией (leaky abstraction — беда любого программиста, включая очень себе сеньоров), то именно сложность и является основной проблемой.
Здравствуйте, SkyDance, Вы писали:
НС>>А я не понимаю, как можно в обсуждении неправильности service locator и правильности DI игнорировать заявления о неправильности service locator. SD>Вы меня с кем-то спутали.
Это не ты написал?
НС>Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн..
Да мало ли что где клеймят.
SD>я уже объяснял, что не вижу смысла код создания зависимостей на Java выносить в отдельные файлы (XML)
А я уже отвечал, что в 2021 году почти никто уже не видит смысла в этом и в свежих контейнерах такой фичи просто нет. Так какой смысл постоянно переводить разговор на это дерьмо мамонта?
НС>> Это можно сказать про любую более менее сложную технологию. SD>Кстати да, сложность — это не достоинство. Это недостаток.
Конечно. Но так редко бывает, что у сложных проблем есть простые решения.
НС>>>Разница тут будет только в случае вызова метода Resolve, что тут все заклеймили позором как антипаттерн..
НС>>Да мало ли что где клеймят.
SD>Я service locator позором не клеймил
Так и процитированный ответ был не тебе.
НС>>А я уже отвечал, что в 2021 году почти никто уже не видит смысла в этом и в свежих контейнерах такой фичи просто нет. Так какой смысл постоянно переводить разговор на это дерьмо мамонта? SD>И как же делают в 2021 году?
В коде.
SD>А код, который был до 2021, тоже уже весь переписали?