Помнится, во времена .NET 1.1 и 2.0 можно было часто увидеть пророчества майкрософтовских евангелистов, мол, скоро любая домохозяйка сможет создавать сайты и писать программы. Большинство программистов посмеялось, но, как выяснилось, кто-то отнёсся к этому серьёзно. Во всяком случае, это объясняет, почему шаблоны проектирования IoC/DI получили второе дыхание в 2000-х, причём даже внутри самой MS (желаю Вам никогда в жизни не столкнуться с SCSF).


С точки зрения теории разработки ПО лично мне гораздо чаще приходилось читать или слышать хвалебные статьи и отзывы об IoC/DI, но, как всегда, критика тоже есть. Можно ознакомиться, например, здесь (англ.), здесь (англ.), тут (Хабр), ещё (англ.). В частности в вину ставится нарушение принципа инкапсуляции в ООП.

Но мне хотелось бы не вдаваться в теологические диспуты (в чём я не считаю себя экспертом), а остановится на трудовых буднях (которые мало, на мой взгляд, освещаются в публикациях).

Действительно, много ли Вы встречали книг или статей по программированию, где указывалось бы на то, что код всегда содержит ошибки (даже калькулятор невозможно покрыть 100% тестированием) и нужно в него вставлять возможности диагностирования ошибок в продуктивной среде, где Вам никто не даст поставить Studio и отладиться? Что если продукт окажется достойным и найдёт своего пользователя, то он обязательно будет дорабатываться, но делать это, вполне вероятно, будут другие люди, с не известно каким уровнем подготовки? Вот я ни одной не могу вспомнить.

Волею судеб В следствие личных и чужих управленческих ошибок, мне довелось запускать проект в гордом одиночестве и погрузиться в код, до чего у меня ранее не было особого желания. Использование в одном из модулей IoC/DI не было самой большой проблемой, но зато самой яркой и запоминающейся, тем более, что мне уже приходилось сталкиваться с этим чудом абстракционистской мысли на предыдущем месте работы.

UPD 2017-04-03 13:45 МСК: Коллеги, заранее прошу меня извинить, что не очень оперативно отвечаю. Тема холиварная, мнений много. Постараюсь отреагировать на все ветки обсуждений.

Итак.

Сложность для понимания


Где-то читал в своё время – то ли у Брукса, то ли у Листера с ДеМарко, точно не помню, – что языки программирования придуманы не для машин, а для людей. Машине, в конце концов, без разницы, забьёте ли вы руками в файл нолики и единички или сначала напишите текстовые команды, а затем откомпилируете в исполняемый код. Компилятору всё равно, будет ли программист вставлять комментарии в свой код или посчитает, что тот является самодокументированным. Интерпретатор JavaScript одинаково обработает обфусцированный, сжатый скрипт и отформатированный, с человекопонятными названиями переменных и функций.

Про архитектуру и парадигмы программирования можно сказать то же самое: всё это придумано для человека, а не для машины, чтобы облегчить ему задачу написания программ, повысить уровень их сложности. Поэтому первым и самым главным минусом, по моему глубокому убеждению, шаблонов IoC/DI является распределение логики по разным кускам проекта, что сильно усложняет понимание и восприятие решения в целом. Точнее – проблема не в собственно разорванности, а в том, что очень сложно всё это связать воедино в статике, только в рантайме.

Если ваша программа состоит из десятка-другого объектов (т.е. это не более 100 файлов с описанием классов и интерфейсов), то воспринять всё это вместе в виде единого целого относительно просто. Мне в своё время довелось сопровождать настольное приложение, созданное на основе Microsoft Smart Client Software Factory (потом ему на смену MS запустила Prism, но уверен, что IoC/DI там так же плотно задействованы), по своему функционалу не такое уж сложное, но состоявшее из пары десятков проектов (в терминах Visual Studio), а это сотни и сотни классов, отвечающих и за DAL, и за логику, и за пользовательский интерфейс, и за событийную модель под ним. Каждый раз, когда на горизонте появлялась задача по добавлению новой фичи, меня начинало слегка колотить изнутри, т.к. всплывала перспектива увлекательно провести несколько дней, чтобы догадаться, куда нужно «воткнуть» обработку нового поля объекта из БД, точнее – по каким классам распиханы зависимости. При слабой связанности классов, поверьте, это не самая тривиальная задача.

Возможно, мой мозг начал костенеть и стал менее восприимчив к новым идеям (хотя IoC/DI были придуманы, кажется, в начале 90-х), но мне сложно понять, чем стал неугоден принцип инкапсуляции из ООП.

Малоинформативные отладочные данные


Вспоминается цитата с башорга:

#define TRUE FALSE //счастливой отладки, уроды (*)

(*) фраза была несколько смягчена, дабы не навлекать на Ресурс.

Смешно, не правда ли? А вот такие шутки штуки я встречал в своём проекте на этапе запуска в ПЭ (и было мне не совсем смешно):

Stack Trace
StructureMap.StructureMapException: StructureMap Exception Code: 202
No Default Instance defined for PluginFamily System.Func`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
at StructureMap.BuildSession.<.ctor>b__0(Type t)
at StructureMap.Util.Cache`2.get_Item(KEY key)
at StructureMap.BuildSession.CreateInstance(Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
at lambda_method(Closure, IArguments )
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance(Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
at lambda_method(Closure, IArguments )
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance(Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
at lambda_method(Closure, IArguments )
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
at StructureMap.BuildSession.CreateInstance(Type pluginType)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ConstructorInstance.Get[T](String propertyName, BuildSession session)
at lambda_method(Closure, IArguments )
at StructureMap.Construction.BuilderCompiler.FuncCompiler`1.<>c__DisplayClass2.<CreateBuilder>b__0(IArguments args)
at StructureMap.Construction.InstanceBuilder.BuildInstance(IArguments args)
at StructureMap.Pipeline.ConstructorInstance.Build(Type pluginType, BuildSession session, IInstanceBuilder builder)
at StructureMap.Pipeline.SmartInstance`1.build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.createRawObject(Type pluginType, BuildSession session)
at StructureMap.Pipeline.Instance.Build(Type pluginType, BuildSession session)
at StructureMap.Pipeline.ObjectBuilder.Resolve(Type pluginType, Instance instance, BuildSession session)
at StructureMap.BuildSession.CreateInstance(Type pluginType, Instance instance)
at StructureMap.Container.GetInstance[T](String instanceKey)
at NNN.BBB.Integration.Uvhd.Dispatcher.Start() in j:\.projects\DDD\trunk\NNN.BBB.UvhdIntegrationService\Dispatcher.cs:line 30

Очень информативно, согласитесь? BBB, DDD, NNN – это я намеренно изменил название проектов и пространства имён, которые указывали на наименование компании-субподрядчика. Но там ничего интересного для отладки не было. Dispatcher.Start() – это запуск службы MS Windows, точка входа в программу. StructureMap – это библиотека IoC. Ни единого упоминания какого-либо из бизнесовых методов, т.к. было сделано всё, чтобы исключить контекст из стека вызова.

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

Нивелирование достоинств компилируемых языков


Как говорится, беда не приходит в одиночку. Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator, который тесно связан с идеей позднего связывания. При этом при компиляции решения не проверяется, соответствует ли сигнатура методов требованиям в точке вызова.

Так и случилось в моём случае из примера выше. В один из бизнесовых методов был добавлен новый параметр. Проект успешно скомпилировался, но отказался запускаться. Мне повезло.

Во-первых, в данном проекте было всего лишь около 50 классов и методом [научного] тыка удалось относительно быстро установить, что нужно ещё доработать класс загрузки конфигурации.

Во-вторых, исключение выбрасывалось в самом начале работы программы, и мимо этого было не пройти. К сожалению, не смог найти другой реальный пример и жизни из этого же проекта, т.к. сразу не отложил «в мемориз», а потом всё затерялось в килотоннах логов. Но инстанцирование обработчиков может производиться не сразу при старте приложения, а по мере необходимости – когда появятся пользовательские данные, требующие соответствующей обработки. Это может произойти спустя часы, дни и даже недели. Однако эффективность упомянутого выше метода поиска ошибок «было – стало» обратно пропорциональна времени, прошедшему с момента проблемного релиза, и количеству релизов, последовавших за ним.

Повышенные требования к уровню подготовки специалистов


Сами по себе сложность восприятия кода и трудности отладки не смертельны. Беда в том, что для целей внедрения, сопровождения и развития продукта нужны люди из плоти и крови. И очевидно, чем сложнее продукт, тем выше требования к уровню подготовки специалистов. А тут уже встают проблемы рынка труда и сферы образования, о которых, полагаю, не нужно никому тут рассказывать: грамотных, опытных спецов найти занимает много времени, содержать не дёшево, удержать и того сложнее. Да и на изучение «матчасти» уходят недели и даже месяцы.
Как следствие из данного аргумента хотел бы выделить ещё 3 уточнения.

  1. Я на своей практике не раз сталкивался с ситуацией, когда первоначальную команду разработчиков постепенно сменяла другая, с менее продвинутыми знаниями в теории программирования и передовых методах разработки софта. Просто потому, что после запуска продукта, бывает, на первый план выходит знание предметной области (например, законодательство, бизнес-процессы целевой аудитории, коммуникативные навыки), а не технологий кодирования. В такой ситуации чем сложнее код, тем быстрее продукт «эволюционирует» в замысловатое ожерелье костылей и заплаток.

  2. Чем сложнее код, тем дольше длится погружение в проект новичка.

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

Заключение


В начале статьи были приведены ссылки на статьи, в некоторых из коих приводятся условия, когда можно применять DI/IoC, а когда этого лучше не делать. Я предлагаю дополнить список, когда НЕ надо использовать эти шаблоны, правилами, скорее, управленческого уровня, нежели из области компьютерных наук. Не используйте таковые шаблоны, если:

  • Вы не уверены на 100%, что на этапе запуска проекта, его стабилизации, опытной и даже промышленной эксплуатации будут оперативно доступны разработчики этого продукта.

  • Проектировщиком и разработчиком системы является один-единственный человек, который (не удивительно) не ведёт подробного документирования архитектуры своего детища.

  • У вас нет основания полагать, что на этапе проектирования Вы предусмотрели все варианты использования продукта и на этапе сдаче проекта или даже после его запуска не возникнет острой потребности в спешном порядке «допиливать» бизнес-логику; или если бизнес-среда, в которой предстоит плавать продукту, слишком изменчива, например, частые изменения регуляторов, конкуренция на рынке, отсутствие чётких стандартов/рекомендаций/практик в отрасли.
Поделиться с друзьями
-->

Комментарии (272)


  1. neru
    01.04.2017 11:12

    Долго думал, в Управление проектами или в Разработку, ну пусть лежит здесь


    1. Bonart
      02.04.2017 03:58
      +1

      Главное, что опубликовано вовремя.


  1. qvx
    01.04.2017 12:10
    +2

    Работаю с двумя реализациями IoC, Ни в одной не встречал ситуации «у нас все плохо, но мы не скажем почему», скорее всего по тому, что живых ситуаций потери зависимостей не так много и все они разжевываются новичкам буквально в первые недели работы. Гораздо сложнее объяснить про контекстно зависимость для части классов. Вот тут реально такие варианты решения «проблем» видел, что хоть вешайся. Тут остро стоит вопрос «я новичок и должен показать, что я умею круто программировать», в итоге — свои реализации стандартных контейнеров, обязательная «самая правильная» реализация синглтона (я уж не знаю, возможно это какое-то неформальное соревнование). А размер проекта вообще на сложность не влияет, если он изначально написан модульно.


    1. neru
      01.04.2017 14:58
      +3

      Вот вы пригласите почитать кого-нибудь со стороны почитать ваш код, и пусть он скажет. :)

      А размер проекта вообще на сложность не влияет

      Вот это поворт :)


      1. Fesor
        01.04.2017 16:22

        Вот вы пригласите почитать кого-нибудь со стороны почитать ваш код, и пусть он скажет. :)

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


        Вот это поворт :)

        Сложность бывает разной. Например бывал на проектах довольно маленьких но сложных в силу предметной области и задачи. И с другой стороны — бывал на проектах где всего много но это все очень просто и система разбита на вполне себе независимые модули. Так же есть проекты где сложность от размера команды нежели проекта, причем это больше сложность в координации.


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


      1. qvx
        02.04.2017 12:59

        и пусть он скажет

        Самое забавное, что все (ну ладно, многие) считают, что они пишут для себя. Отсюда и такое как у Вас отношение. Пишите так, чтобы другие сказали, что вы пишете понятно. Исли изначально исходить из таких предпосылок, то и вопросов о читаемости не возникнет.
        Вот это поворт

        Ну да. Тут главное понять что такое «большой проект» и «сложный проект» — полтора миллиона строк для управления продажами или 12 тысяч строк на ассемблере, которые будут работать на орбите в течении 3х лет. Так вот второе это «сложный», а первое совсем наоборот. Довести любой учетный проект до состояния точки невозврата возможно, но довольно быстро возникает ситуация, когда само управление изменениями становится большей проблемой чем, собственно, внесение изменений. Тут мы вспоминаем про Фаулера и начинаем излагать умные мысли, хотя на самом деле нужно было изначально подумать «а как мы будем изменять», ведь изменение — основа всех учетных систем. И IOC в данном случае как раз путь к решению этой проблемы на уровне архитектуры


        1. neru
          02.04.2017 19:58

          Самое забавное, что все (ну ладно, многие) считают, что они пишут для себя. Отсюда и такое как у Вас отношение. Пишите так, чтобы другие сказали, что вы пишете понятно. Исли изначально исходить из таких предпосылок, то и вопросов о читаемости не возникнет.

          Вот и про то же. Причём и вполне себе опытные спецы могут так делать. Как я писал в начале статьи, на мой ИМХО, очень мало акцентируется внимания в статьях и книгах, поэтому, когда мозг проектировщика начинает усиленно переваривать ТЗ, на уровне подкорки не возникает желания взглянуть на продукт с точки зрения тех, кто будет его запускать, сопровождать и развивать.

          Тут мы вспоминаем про Фаулера и начинаем излагать умные мысли, хотя на самом деле нужно было изначально подумать «а как мы будем изменять», ведь изменение — основа всех учетных систем. И IOC в данном случае как раз путь к решению этой проблемы на уровне архитектуры

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


  1. dmitry_dvm
    01.04.2017 12:17
    +10

    Ну не знаю насчет сложности того же Prism, он сейчас в опенсорсе и заметно похудел. Но однажды мне пришлось полностью переписать DAL (заменить Refit на RestSharp) и я благодарил вселенную за то, что люди изобрели IoC, потому что вся процедура заняла буквально час-два. Даже не знаю, сколько пришлось бы ковыряться в противном случае.


    1. rraderio
      03.04.2017 10:09
      -4

      Даже не знаю, сколько пришлось бы ковыряться в противном случае.

      Replace by RegExp? По времени долно быть также.


      1. rraderio
        03.04.2017 10:31
        -1

        Что обычно надо поменять

        import package.SomeService
        

        тут все ясно, с RegExp нет проблем

        someService.someMethod(...)
        

        тут тоже нет особых проблем

        вроде все, за что минусы?


  1. Fesor
    01.04.2017 13:02
    +9

    точнее – по каким классам распиханы зависимости. При слабой связанности классов, поверьте, это не самая тривиальная задача.

    под слабой связанности вы тут именно cohesion или coupling имеете ввиду?


    но мне сложно понять, чем стал неугоден принцип инкапсуляции из ООП.

    Интересно каким образом DI/IoC нарушает инкапсуляцию. Клиентский код как не знал о зависимостях используемых объектов так и не знает. Точно так же как объект который мы хотим получить не должен знать ничего о жизненном цикле своих зависимостей.


    Ну и IoC опять же способствует тому чтобы скрывать от объектов не интересующие их вещи. Сам принцип Don't call us we call you об этом.


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

    Может быть проблема как раз таки в низкой квалификации? Ну то есть решать надо именно эту проблему, а не симптомы устранять.


    Малоинформативные отладочные данные

    То есть это в целом проблема инстанциирования большого графа зависимостей. нет? Причем тут IoC/DI? Ну и еще есть простой лайфхак. если между кодом который вызывает и кодом который выполняет тонны абстракций, их можно спрятать/выкидывать грепом. Как правило у таких вещей будет вполне себе явный нэймспейс и можно легко фильтровать стэктрейсы.


    Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator который тесно связан с идеей позднего связывания.

    Вообще тут очень тонкий момент. Если ваш сервис локатор вставляет палки в колеса и не позволяет выводить типы, значит это плохой сервис локатор. То есть все упирается исключительно в конкретную реализацию а не в концепцию.


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

    причем тут IoC/DI? Если система спроектирована плохо, если абстракции используемые текут, если для того что бы разобраться в чем-то надо прошерстить всю систему… то у меня есть вопросы к подобного рода проектам.


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


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


    Но достаточно посадить его на часик-два и рассказать о проекте да что бы он почитал спеки/ассептанс тесты, и код начинает восприниматься уже вполне логично. И становится понятно почему штука по покупке валют лежит там а трансфер валют там.


    Не используйте таковые шаблоны, если:

    Я так и не увидел предлагаемой альтернативы. Более того я так и не понял что плохого вы видите в IoC. Есть подозрение что под IoC вы имеете ввиду контейнер зависимостей а не сам принцип.


    1. sshikov
      01.04.2017 13:27
      +11

      +1 Особенно вот про это:

      >Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator, который тесно связан с идеей позднего связывания.

      Потому что обычно как раз _не_ идут. DI как правило избавляет от надобности в Service Locator совсем.


      1. mayorovp
        01.04.2017 14:57
        +7

        Поправка: хороший DI. Зачастую под так называемым IoC программисты понимают раскиданные по всему коду вызовы Resolve. Термин Composition Root — так воовсе тарабарщина для многих :-(


      1. yarosroman
        01.04.2017 15:11
        +3

        Да и многие наоборот считают Service Locator антипаттерном (https://www.manning.com/books/dependency-injection-in-dot-net).


    1. neru
      01.04.2017 15:11

      под слабой связанности вы тут именно cohesion или coupling имеете ввиду?

      Имею в виду инстанцирование в runtime-е.
      Интересно каким образом DI/IoC нарушает инкапсуляцию.

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

      Разумеется. Об этом и речь. Других под рукой не было :)
      То есть это в целом проблема инстанциирования большого графа зависимостей. нет? Причем тут IoC/DI? Ну и еще есть простой лайфхак. если между кодом который вызывает и кодом который выполняет тонны абстракций, их можно спрятать/выкидывать грепом. Как правило у таких вещей будет вполне себе явный нэймспейс и можно легко фильтровать стэктрейсы

      Вполне допускаю, что есть хорошие способы. Вопрос к авторам кода, почему их не использовали. Возможно, на тот момент какая-то книжка была прочитана наполовину. :)
      причем тут IoC/DI? Если система спроектирована плохо,

      Разумеется. Плохо. С IoC/DI это сделать очень просто. ИМХО.
      Я так и не увидел предлагаемой альтернативы. Более того я так и не понял что плохого вы видите в IoC. Есть подозрение что под IoC вы имеете ввиду контейнер зависимостей а не сам принцип.

      Наверное, альтернатив может быть много. Тут я не берусь навязывать.


      1. Fesor
        01.04.2017 16:33
        +4

        Имею в виду инстанцирование в runtime-е.

        простите, а инстанциирование бывает не в рантайме?


        Реализация некой логики выносится не понятно куда

        так проблема в IoC или в том что:


        • какая-то логика вынесена в конструктор
        • какая-то логика запихнута непонятно куда
        • все очень плохо с coheasion.

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

        я плохо понимаю причем тут все таки DI. Откуда взялись анонимные вызовы какие-то?


        С IoC/DI это сделать очень просто. ИМХО.

        Эм… а с фабриками? Ну мол предположим что для каждой сервисной фигни у вас своя фабрика. Все зависимости для каждого объекта в этом случае будут описываться явно. Или вы предлагаете размазать знания о цикле жизни объектов по клиентскому коду (код который использует зависимости)?


        Наверное, альтернатив может быть много. Тут я не берусь навязывать.

        какие еще альтернатив… https://en.wikipedia.org/wiki/Inversion_of_control


        1. nolled
          02.04.2017 18:37

          > простите, а инстанциирование бывает не в рантайме?

          Автор имел ввиду coupling под тем.

          > Или вы предлагаете размазать знания о цикле жизни объектов по клиентскому коду (код который использует зависимости)?

          У меня сейчас оочень большой проект над которым работало много разных людей последние 8 лет. Не смотря на использование Dependency injection, это на практике не остановило размазывание о котором вы описываете. И я бы наверено действительно предпочел бы фабрику, что дало бы анализировать код статически.


      1. yarosroman
        01.04.2017 18:32
        +4

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

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


        1. vintage
          02.04.2017 22:33
          -1

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


          1. Fesor
            02.04.2017 22:54
            +1

            Да, но тут надо смотреть конкретный случай, ибо если с декомпозицией все хорошо и мы разделяли классы с позиции coheasion и зоны ответственности, то это будет только хорошо с точки зрения абстракции.


            1. vintage
              02.04.2017 23:03

              Именно. Не все зависимости нужно выносить. И аргумент про "проще тестировать" тут не релевантен ибо тестовые фреймворки должны уметь тестировать любой код, а не только вывернутый кишками наружу.


          1. VolCh
            03.04.2017 06:16

            Внешний код внешнему коду рознь. Есть клиентский код, а есть вынесенный код. Вынос кода инициализации, связывания не уменьшает уровень абстракции для клиентского кода.


            1. vintage
              03.04.2017 07:43
              -1

              Вынесенный куда?


              1. VolCh
                03.04.2017 08:06

                В инфраструктуру типа DI-контейнера.


                1. vintage
                  03.04.2017 09:09
                  -3

                  От кого и когда этот DI-контейнер узнаёт как инициализировать объект?


                  1. VolCh
                    03.04.2017 10:11

                    От разработчика на этапе билда или администратора на этапе запуска.


                    1. vintage
                      03.04.2017 11:39
                      -2

                      То есть от самого дальнего клиента. Абстракция протекла и затопила весь дом.


                      1. VolCh
                        03.04.2017 11:46

                        Не от клиента, от инфраструктуры.


                        1. vintage
                          03.04.2017 12:30
                          -2

                          Можете называть это хоть "эфиром". Суть от этого не меняется — чтобы использовать библиотеку необходимо вручную подготовить для неё правильное "окружение", иначе она просто не заработает.


                          1. VolCh
                            03.04.2017 12:54

                            Меняется. Не клиент библиотеки должен это окружение готовить в общем случае.


                            1. vintage
                              03.04.2017 15:24
                              -2

                              Тот, кто использует. А использует программист.


                              1. VolCh
                                03.04.2017 15:32

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


                                1. vintage
                                  03.04.2017 15:45
                                  -2

                                  Вы никогда не шарите код между приложениями?


                                  1. VolCh
                                    03.04.2017 16:33

                                    Шарю. А какое это имеет значение?


                                    1. vintage
                                      03.04.2017 17:00
                                      -2

                                      "Инфраструктуру" тоже шарите?


                                      1. VolCh
                                        03.04.2017 17:36

                                        Конечно, не писать же для каждого проекта с нуля.


                                        1. vintage
                                          03.04.2017 17:45
                                          -1

                                          Отлично, не поделитесь либой?


                                          1. VolCh
                                            03.04.2017 18:17

                                            1. vintage
                                              03.04.2017 18:50
                                              -2

                                              Не DI, а вашей либой, которая на DI завязана.


                                              1. VolCh
                                                03.04.2017 18:52
                                                +2

                                                Либы на DI не завязаны. У них есть параметры конструкторов и сеттеры, фабрики и т. п., которые вызывает DI-контейнер. Замена DI или его полное отсутствие никак на либу не влияют.


                                              1. Fesor
                                                03.04.2017 19:01
                                                +1

                                                есть либа, она ничего не знает о DI и т.д. Есть сервис провайдеры/экстеншены/модули под конкретный DI контейнер которые занимаются подготовкой окружения. Часто это отдельный пакет. Клей который все склеивает. Живет он так же снаружи приложения, оно ничего не знает о контейнерах и фреймворках… ну как-то так.


                                                1. vintage
                                                  03.04.2017 21:56
                                                  -2

                                                  То есть либа у вас — это как мебель из Икеи — вот вам фанера с дырками (классы либы), вот вам пакет шурупелей (провайдеры/экстеншены/модули) — собирайте сами. Абстракцией тут и не пахнет.


                                                  1. lair
                                                    03.04.2017 22:25
                                                    +2

                                                    Ну посмотрите вы уже, как это в приличных местах сделано. Внутри у компонента есть DI, который работает по умолчанию — то есть все необходимые для работы сервисы уже зарегистрированы. Те, которые вам надо заменить (например, у вас свой persistence), заменяются через простой API. Сторонние реализации предоставляют свои собственные расширения к интерфейсу. Особо желающие могут написать адаптер, который возьмет все нужные реализации из внешнего контейнера и запихнет во внутренний, но мне не понадобилось.


                                                    И все. Никакой фанеры, никаких шурупов. Просто залейте бензин сюда.


                                                    1. vintage
                                                      03.04.2017 23:47

                                                      Я не специалист по .NET, так что давать мне ссылку на мутные описания не пойми чего с кусками кода, не пойми как связанными, не имеет смысла — я не разберусь. Но судя по вашему описанию, библиотека не выносит зависимости во вне, а предоставляет дополнительное низкоуровневое апи для подмены реализаций. И это замечательно. Клиент может сам выбирать уровень абстракций, на который хочет погружаться. Выше же описывалась совсем иная архитектура, "без зависимостей и без внутренних DI-контейнеров".


                                                      1. lair
                                                        04.04.2017 00:02
                                                        +1

                                                        Но судя по вашему описанию, библиотека не выносит зависимости во вне, а предоставляет дополнительное низкоуровневое апи для подмены реализаций.

                                                        Ненужные — выносит.


                                                        Выше же описывалась совсем иная архитектура, "без зависимостей и без внутренних DI-контейнеров"

                                                        Цитата:


                                                        Либы на DI не завязаны. У них есть параметры конструкторов и сеттеры, фабрики и т. п., которые вызывает DI-контейнер. Замена DI или его полное отсутствие никак на либу не влияют.

                                                        "Либа" не завязана на внешний DI. У нее есть фабрика (на которую я вам дал ссылку), которую можно сконфигурить напрямую или завести на внешний DI-контейнер. Внутри у "либы" — сквозной DI, есть у нее контейнер или нет — для потребителя не важно.


                                                        И да, если бы внутри не было Dependency Inversion, это все было бы невозможно.


                                                        1. vintage
                                                          04.04.2017 07:36
                                                          -3

                                                          "фабрика, которую можно сконфигурировать" — сервис локатор?


                                                          1. lair
                                                            04.04.2017 11:29
                                                            +1

                                                            Нет, зачем. Обычный composition root.


                                                  1. babylon
                                                    03.04.2017 22:27
                                                    -1

                                                    Не инкапсуляцию (объединение состояния и поведения), а абстракцию (сокрытие внутренней сложности за простым интерфейсом)


                                                    Не совсем точно…
                                                    Интерфейсы не всегда просты. Они скрывают с помощью абстрактных классов реализацию. Фабрика например. Можете сделать фабрику фабрик. Навтыкать сервис локейторов для словарей с реализациями или с фабриками. Зайдите на flasher.ru. Там эта тема обсосана со всех сторон много лет назад. Тут абстракция обычно привязана к понятию абстрактный класс, а не в широком смысле как вы любите.


                                                    1. vintage
                                                      03.04.2017 23:57
                                                      -1

                                                      Вы бы ещё в Гугл послали — уж там-то точно вообще все темы обсосали :-) Но зачем мне это однобокое представление об абстракции как о "абстрактном классе"? Задача абстракций — упрощать работу за счёт скрытия деталей. Любых деталей. Будь то детали кода конкретных методов или детали развесистости внутренних интерфейсов.


                                                      1. VolCh
                                                        04.04.2017 09:06

                                                        Так DI-контейнері именно что скрывают от клиентов класса детали его реализации в части инстанцирования и инициализации, абстрагируют инстанцирование и инициализацию до вызова чего-то вроде container.get('name') или container.getName(). Сам класс может иметь внутренние захардкоженные зависимости, они могут инжектиться контейнером или фабрикой через конструктор, сеттер, свойства или ещё как, контейнер может подставлять не сам класс, на который рассчитывает клиент, а его наследников, клиент может рассчитывать вообще не на конкретный класс, а на абстрактный, а то и интерфейс. Он полностью абстрагирован от деталей инстанцирования и инициализации, просто обращается к контейнеру и получает готовый к употреблению инстанс класса, реализующий нужный ему интерфейс, ничего не зная о его зависимостях.


          1. Bonart
            03.04.2017 16:25

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

            Нет, не уменьшает.
            Вынос наружу зависимости меняет только инстанцирование, но не использование.
            Невынос наружу вынуждает привязку к конкретным инструментам (сборкам) без возможности для их замены или оптимизации — абстракция все равно протекает, но опаснее, так как протечка неявная.


            1. vintage
              03.04.2017 17:04
              -1

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

              Приведите законченный пример.


              без возможности для их замены или оптимизации — абстракция все равно протекает

              Вы точно знаете, что такое "абстракция"?


  1. dymanoid
    01.04.2017 14:09
    +6

    Многие считают Service Locator вообще анти-паттерном. Да и в том же Prism не рекомендуют его использовать.


  1. vvovas
    01.04.2017 14:54
    +4

    Хотел бы заступиться за Smart Client. На мой взгляд — это очень хороший набор инструментов для разработки приложений. В нем, конечно, есть недостатки, но сама суть — это шедевр. Я просто могу предположить, что либо вы не до конца поняли как устроен Smart Client, либо ваш проект использовал его не совсем правильно. С продуманной архитектурой, которую понимают все разработчики, вопросов поиска классов не возникает. У меня был опыт работы с проектом на Smart Client, который начали писать не особо думая о том, как правильно это делать (Вау! Глобальные события! Давайте их везде использовать!). Но потом, когда все привели в порядок не стало вопросов о том, где писать нужный кусок кода. Все понимали за что отвечает view, presenter, workitem, module и т.п.

    На самом деле к DI/IoC надо прийти. Не пихать его бездумно везде, потому что это «модно, стильно, молодежно», а действительно понимать зачем он там. Я его начал использовать, когда в мелком проекте было по 2-3 реализации каждого этапа работы программы. Мне просто надоело комментировать конкретные реализации для запуска программы с определенной конфигурацией. Вот тут и пригодился DI/IoC, который одной строчкой мог переключить набор реализаций на запуске.

    Все нужно уметь использовать и понимать, что как и зачем. Я тоже не люблю DI/IoC в проектах где всегда одна реализация, но когда-нибудь или «а вдруг!»… Обычно это никогда.


    1. areht
      01.04.2017 15:16
      +3

      > Обычно это никогда.

      Обычно это моки в тестах


      1. vvovas
        01.04.2017 15:39
        +3

        Если возникает вопрос зачем DI, то о тестах, к сожалению, речи не идет. А так, да — моки в тестах.


      1. qw1
        01.04.2017 15:52

        Или это система толстый клиент + сервер. Очень удобно все зависимости получать через IoC, а при старте приложения в едином месте указывать, какие реализации запускаются на клиенте, а какие заменены прокси-классами для вызова их на сервере через SOAP/WCF.


    1. neru
      01.04.2017 15:18
      -5

      Хотел бы заступиться за Smart Client.

      Пока у меня был программист, который стоял у истоков создания этого приложения, который понимал всю эту штуку в целом, меня всё устраивало. Проблемы начались, когда я остался один.
      На самом деле к DI/IoC надо прийти.

      Безусловно. С этим никто не спорит. Но с точки зрения РП, как можно в самом начале оценить, будет разработчик правильно использовать или нет? Я могу поверить, что в этом есть что-то изящное, но простым смертным не всегда доступное. :)


      1. vvovas
        01.04.2017 16:06
        +5

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

        Ну, так это проблема не Smart Client, а организации работы в команде. Если у вас только один понимает архитектуру проекта, то это закономерно приведет к таким последствиям, вне зависимости от того, какие инструменты, библиотеки и т.п. вы используете.

        Безусловно. С этим никто не спорит. Но с точки зрения РП, как можно в самом начале оценить, будет разработчик правильно использовать или нет? Я могу поверить, что в этом есть что-то изящное, но простым смертным не всегда доступное. :)

        Ну, я вот сейчас пишу проект на Smart Client — в самом начале выделил 2 недели на то, чтобы полностью продумать всю структуру проекта. Как view будет общаться с presenter'ом, как система будет реагировать на команды из view и т.п. Получил понятную, удобную и простую картину всего приложения. Нет вопросов, где писать каждый кусок кода.
        Да, это личный проект, я могу потратить 2 недели на это(да и в реальном проекте, это тоже не так много). Но теперь, если со мной этот проект будет кто-то писать, то первое что будет сделано — это рассказано об архитектуре, почему так и какие плюсы минусы.
        Если программист говорит, что что-то надо использовать, потому что это последнее достижение или все сейчас используют — то это ничего не говорит о понимании. Если он может полностью рассказать о том, как это использовать в рамках проекта, какие плюсы это принесет, какие ограничения есть и как это заменить, если придется переходить на что-то другое, то он понимает как правильно работать.


        1. neru
          02.04.2017 09:51
          -2

          Ну, так это проблема не Smart Client, а организации работы в команде. Если у вас только один понимает архитектуру проекта, то это закономерно приведет к таким последствиям, вне зависимости от того, какие инструменты, библиотеки и т.п. вы используете.

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


          1. qw1
            02.04.2017 11:35
            +1

            Тут вопрос, где остановиться в упрощениях.

            Можно в проекте запретить generics или linq и писать всё в стиле FORTRAN-77 (а то вдруг идиоты стажёры не поймут).


          1. VolCh
            02.04.2017 13:17
            +1

            А можно просто делать просто, чтобы было понятно даже стажёру.

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

            Разработчик использует известные ему паттерны, чтобы упростить свою работу по разработке и поддержке системы, которую несколько десятилетий назад представить себе было нельзя("невозможно на современной элементной базе" © ), а десятилетие назад её реализация была доступна только мировым гигантам.


            Сложные системы наверное невозможно написать так, чтобы они были понятны стажеру, только-только изучившему ЯП и не знающему предметную область и при этом такое упрощение не мешало специалистам эффективно делать свою работу.


      1. areht
        01.04.2017 16:17

        > Но с точки зрения РП, как можно в самом начале оценить

        РП оценивающий самостоятельно «простым смертным не всегда доступное» вызывает недоумение. Да и вообще непонятно зачем ему это. (Хотя… Сегодня — можно.)

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


        1. neru
          02.04.2017 09:53

          У вас РП лазает в код?

          Если старый бюджет съеден, а новый будет только после запуска, то запускать придётся самому.


          1. areht
            02.04.2017 18:54
            +1

            1) Я не спрашивал кто будет лезть в код, когда программист уволится. Я спрашивал «кто отвечает за код в процессе». Если никто не отвечает, то вам и без DI наворотят, вплоть до "#define TRUE FALSE", и спросить не с кого.

            2) Ну так Вы и оценивайте сможете ли Вы правильно использовать.Если нет — не нужны статьи про DI, это нефункциональное требование «мне лень в этом разбираться — делаем по-старинке».

            3) Так получается, вы и не РП. Ну или «и жнец и кузнец и на дуде игрец», плохо справляющийся со всем (ни в DI разобраться времени не нашли, и бюджет съеден, и код не отревьюен).

            Вы, как РП, сами выстроили процесс, где проблемы. Ну или у вас проекты часов на 40, где думать о DI некогда, трясти надо. Но проблема не в самом DI. Выбейте бюджет в 3 раза больше, будет время порефлексировать.


            1. neru
              02.04.2017 20:15
              -1

              1) Я не спрашивал кто будет лезть в код, когда программист уволится. Я спрашивал «кто отвечает за код в процессе». Если никто не отвечает, то вам и без DI наворотят, вплоть до "#define TRUE FALSE", и спросить не с кого.

              Тогда может возникнуть вопрос, а кто отвечает за тех, кто отвечает за код. Ну был архитектор, ни которого я полагался. И по факту никакого времени у него не было, чтобы реально оценить решение.А вообще не понятно, куда вы уводите дискуссию.
              Отвечает за проект РП. Он и виноват в итоге. С себя я вины не снимаю. Но и не вижу противоречий с моим посылом, что при написании программы, было неплохо подумать о тех людях, которые с этим потом будут разибираться. Может, это будет РП, может — программист-стажёр, может — перешедший с других технологий.

              2) Ну так Вы и оценивайте сможете ли Вы правильно использовать.Если нет — не нужны статьи про DI, это нефункциональное требование «мне лень в этом разбираться — делаем по-старинке».

              Мне кажется, что вы недостаточно хорошо меня знаете, чтобы с такой лёгкостью обвинять меня в лени. Потрудитесь, прощу вас, почитать мою статью ещё раз (в особенности — выводы) и указать, что именно в ней не так сказано

              3) Так получается, вы и не РП. Ну или «и жнец и кузнец и на дуде игрец», плохо справляющийся со всем (ни в DI разобраться времени не нашли, и бюджет съеден, и код не отревьюен).

              О своих управленческих ошибках я не забыл упомянуть в своей статье. т.ч. на чём вы меня пытаетесь подловить?
              Жизнь оказывается несколько сложнее, чем наши идеалистические представления. Полицейские должны ловить преступников, а не копаться в кипах бумаг, чиновники должны исходить с позиций долга перед обществом и ставить интересы социума выше своих личных, врачи должна оказывать квалифицированную помощь бесплатно. Ну должны. Тут вопрос, скорее, философский. Сделать так, чтобы наверняка. Или наваять по науке, а потом сетовать на окружающий мир за непонятый замысел. Мне ближе 1-ый вариант.


              1. areht
                02.04.2017 21:33

                > Может, это будет РП, может — программист-стажёр, может — перешедший с других технологий.

                А может просто не брать разбираться дилетантов?

                > указать, что именно в ней не так сказано

                Да вам другие расписали подробно. В статье вообще не про DI.

                И в лени я вас не обвинял, вы наверняка чем-то важным по 12 часов в сутки на работе занимаетесь. Просто это не попытки разобраться с DI, а попытки закопать неугодный DI (консерватизм+confirmation bias).

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

                > Сделать так, чтобы наверняка. Или наваять по науке, а потом сетовать на окружающий мир за непонятый замысел. Мне ближе 1-ый вариант.

                Ну я примерно об этом и говорю. Если бы Вы были президентом — Вы бы запретили программистам использовать DI, а врачам антибиотики потому, что Вам непонятен замысел, а доделывать и долечивать придётся, если что, вам. Это путь в никуда.

                Если у архитектора нет времени — надо уволить его и нанять того, у которого есть время на свои обязанности.
                Если чиновник ставит свои интересы выше — его сменяют (так или иначе).
                Если врач лечит ангину слабительным — его надо уволить, а не писать статьи, что лекарства не надо применять, если «У вас нет основания полагать, что на этапе проектирования Вы предусмотрели все варианты использования продукта».

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

                > Тогда может возникнуть вопрос, а кто отвечает за тех, кто отвечает за код.

                РП отвечает не за код, а за результат.
                Если РП для результата нужен хороший код — он назначает того, кого уволят, если код будет плохим. Поэтому нет, не только РП виноват в итоге. И пока свою долю вины (за свой участок принятия решений) не прочувствуют остальные участники процесса — ничего не заработает.


      1. Casus
        03.04.2017 15:03

        Существует такой критерий оценки проекта как Bus factor.
        По посту и комментам сложилось впечатление: что дело было не бобине...


    1. qw1
      01.04.2017 15:57

      Все нужно уметь использовать и понимать, что как и зачем. Я тоже не люблю DI/IoC в проектах где всегда одна реализация, но когда-нибудь или «а вдруг!»
      Как по мне, это кошмар, если часть сервисов подключена напрямую, а часть через DI. Всё должно быть единообразно, чтобы не ломать голову, зачем тут так, а там по-другому.

      Я его начал использовать, когда в мелком проекте было по 2-3 реализации каждого этапа работы программы.
      Вы придумали своё применение этому механизму. Прочитайте SOLID, там однозначно указано, для чего применяется DI, и это не для выбора стратегии в определенном месте программы.


  1. vba
    01.04.2017 15:33

    Пользуйтесь ФП, каррингом и композициями функций и ничего не будете нарушать, и будет вам счастье.


  1. alek_sys
    01.04.2017 15:40
    +1

    По своим личным ощущениям, я хочу сказать, что отношение разработчика к DI зависит от платформы / реализации этого самого DI. Я абсолютно так же был удивлён как сообщество хорошо отзывается о DI практиках, после активного использования StructureMap в .Net проектах. Много boilerplate кода, регистрация каждой мелочи, странные ошибки и куча ограничений. Но все изменилось когда я начал работать со Spring Framework и его DI — вот теперь я не могу представить крупный проект без DI. А разница в том, что в Spring DI это естественная часть платформы, там все работает через контейнер. А благодаря функциям типа Component Scan отпадает нужда в огромных классах для регистрации компонентов. Все системные компоненты самого Spring можно точно так же включать в зависимости в своём коде. И настройки не требуется вообще — по умолчанию все контроллеры резолвятся через DI и можно без усилий начать строить свою injection hierarchy. Точно так же можно менять компоненты системы, переконфигурируя DI — и Spring подхватит нужные пользовательские бины вместо своих. То же самое в тестировании — можно брать готовое приложение и мокать компоненты.

    А вот в .net (по крайней мере до .net core) все DI контейнеры что я видел — монструозны и настолько чужеродны платформе, что создают больше проблем, чем их решают. Надеюсь, это поменяется с выходом .net core, где DI это вроде бы часть платформы.


    1. Bonart
      02.04.2017 20:29
      +1

      Spring как недостижимый идеал для дотнетных контейнеров — хорошая первоапрельская шутка.


    1. RouR
      03.04.2017 00:31
      +1

      В .Net MVC очень хорошо интегрированы разные DI. Лично я предпочитаю Autofac.
      Но у автора десктоп-приложение, наверняка с множеством OnPropertyChanged, может поэтому там DI «не зашел».


  1. igor_suhorukov
    01.04.2017 16:15
    +3

    Статья похожа на первоапрельскую шутку!


    1. samizdam
      01.04.2017 22:13
      +3

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


      1. neru
        02.04.2017 09:57
        -5

        Про сферических коней в вакууме я уже писал выше. Когда вы несколько дней подряд посидите по 12-16ч на работе или 3-4 недели без выходных, я посмотрю на вас, с каким энтузиазмом вы будете писать автотесты.
        В-вторых, организация тестовой среды в интеграционном проекте часто бывает делом далеко не тривиальным.


        1. Fesor
          02.04.2017 10:54
          +2

          с каким энтузиазмом вы будете писать автотесты.

          я бывал в ситуациях при которых из-за отсутствия этих самых автотестов приходилось сидеть по 12 часов без выходных где-то месяц. Ну то есть все очень просто, отсутствие тестов делало нахождение регрессий очень проблемным занятием. Более того, поскольку о проблеме вы узнавали хорошо если на следущий день, уже не так просто вспомнить что такого вы меняли, сложнее находить причину проблемы. Как следствие — большой расход времени на отладку и багфикс. Ну и далее, дедлайн никто двигать не будет и потому приходится выходить еще и на выходных что бы хоть как-то успеть.


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


          Так что все это просто отговорки.


        1. samizdam
          02.04.2017 10:56

          Так по-конски перерабатывать это просто не профессионально. В том числе со стороны менеджмента, который это допускает. Единственный сценарий это джун с горящими глазами, которого взяли чтобы всему учился, в т.ч. в свободное время. Но в этом случае, пускать править production код его пожалуй не стоит. А тесты я пишу с энтузиазмом, честно, но раньше кода. Поэтому вашу боль, мне наверно не понять.


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


        1. Bonart
          02.04.2017 12:56
          +1

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


  1. Akirus98
    01.04.2017 16:19

    Одним из примеров хорошего DI я считаю реализацию Nucleus в ATG, где в системе существует много компонентов с легкостью их переопределения и создания новых. Хотя платформа конечно не очень популярна из-за ее узконаправленности.


  1. AlexPu
    01.04.2017 21:56
    +3

    Последние лет 15 мой код всегда содержит все то, что так пугает автора статьи… предложение почитать чужой код с DI меня лично не пугает, и честно говоря давненько уже не видел коллег, у которых это вызывает какие либо проблемы… Даже молодые разработчики (я бы сказал особенно они) оказываются чрезвычайно восприимчивыми к эти идеям… Ну по крайней мере осваивают их без того внутреннего сопротивления которое было у меня, и которое до сих пор есть у автора статьи.


  1. Bonart
    02.04.2017 04:23
    +2

    С точки зрения теории разработки ПО лично мне гораздо чаще приходилось читать или слышать хвалебные статьи и отзывы об IoC/DI, но, как всегда, критика тоже есть. Можно ознакомиться, например, здесь (англ.), здесь (англ.), тут (хабр), ещё (англ.). В частности в вину ставится нарушение принципа инкапсуляции в ООП.

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


  1. Bonart
    02.04.2017 04:43
    +3

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

    "Распределение логики по разным кускам проекта" — это обычная декомпозиция, которая применяется для упрощения понимания и реализации отдельных задач. Плохо проведенная декомпозиция — проблема того, кто проектировал, а никак не DI/IoC


    Возможно, мой мозг начал костенеть и стал менее восприимчив к новым идеям (хотя IoC/DI были придуманы, кажется, в начале 90-х), но мне сложно понять, чем стал неугоден принцип инкапсуляции из ООП.

    Никакого противоречия между DI и инкапсуляцией нет.


  1. Bonart
    02.04.2017 05:04

    BBB, DDD, NNN – это я намеренно изменил название проектов и пространства имён, которые указывали на наименование компании-субподрядчика. Но там ничего интересного для отладки не было. Dispatcher.Start() – это запуск службы MS Windows, точка входа в программу. StructureMap – это библиотека IoC. Ни единого упоминания какого-либо из бизнесовых методов, т.к. было сделано всё, чтобы исключить контекст из стека вызова.

    1. В любом UI приложении в стеке очень много инфраструктуры и очень мало бизнеса и это не проблема, а особенность. C DI-контейнерами все то же самое — служебные строки в стеке моно спокойно игнорировать: достаточно строк из бизнес-классов и текста ошибки.
    2. DI не заставляет вас использовать конкретный контейнер. Если вам не нравится сообщения конкретного контейнера — замените на удобный вам или вообще обходитесь без него, паттерн позволяет.
    3. Если проблема в параметрах не конструктора, а другого метода — не используйте Method Injection и будет вам счастье.


  1. Bonart
    02.04.2017 05:14
    +3

    Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator, который тесно связан с идеей позднего связывания.

    Service Locator с точки зрения DI — антипаттерн. DI-контейнер технически легко использовать как Service Locator, но это классическое забивание гвоздей микроскопом.


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

    Это цена гибкости настройки контейнера, которую вас никто не заставляет платить. Пример для Autofac:


    // Гибкий вариант
    builder.RegisterType<A>().As<IA>();
    
    // Жесткий вариант
    builder.Register<IA>(c => new A(c.Resolve<IB>(), c.Resolve<IC>()));
    

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


  1. Bonart
    02.04.2017 05:23
    +1

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

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


  1. Sevlyar
    02.04.2017 05:32
    +5

    У меня складывается впечатление, что автор просто или путает или отождествляет DI/IoC с Service Locator. Надо отделить мухи от котлет. Почему в статье нет пояснения к используемой терминологии?

    DI — Dependency Injection, внедрение зависимостей, на самом деле очень банальная вещь: если вы определили конструктор с параметрами, вы уже воспользовались внедрением зависимостей через параметры конструктора. Различают также внедрение зависимостей через метод класса (в котором иногда выделяют две разновидности). Но я бы не рекомендовал пользоваться последним методом без необходимости, т.к. способ внедрения зависимостей через параметры конструктора более нагляден.

    IoC — Inversion of Control, наилучшая формулировка, на мой взгляд такая: «Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Первая рекомендация вытекающая из такой формулировки это: «Используйте интерфейсы для определения зависимостей». Следование только этому правилу уже позволит построить менее связанную архитектуру, которую очень просто тестировать с помощью модульных тестов и легко изменять реализацию какой-то части, при необходимости.

    Очень рекомендую почитать про DI и IoC на англоязычной википедии.

    Далее, если у вас очень сложный проект (сотни классов), то вы можете упростить себе жизнь (или усложнить) воспользовавшись Service Locator. Использование Service Locator невозможно без DI и IoC, но не наоборот: если вы используете DI и IoC, то это не означает автоматическое использование Service Locator.


    1. PHmaster
      02.04.2017 11:13
      +1

      Вы перепутали инверсию зависимостей с инверсией контроля. А автор вообще все в одну кучу смешал.


    1. babylon
      02.04.2017 21:46

      Абстракции не зависят от реализации по определению. Или это не абстракции. Интерфейсы скрывают реализации, но не скрывают зависимости. Не встречал зависимость интерфейсов, но интерфейс зависимостей, которые чаще банально реализует наследование имеет место быть. Описали ТЗ на уровне интерфейсов и забыли. Что и как там нагородят в конкретных реализациях совершенно неважно. Главное чтобы была имплементация заданных интерфейсом методов.


  1. Bonart
    02.04.2017 05:36
    +3

    Не используйте таковые шаблоны, если:
    Вы не уверены на 100%, что на этапе запуска проекта, его стабилизации, опытной и даже промышленной эксплуатации будут оперативно доступны разработчики этого продукта.

    В проекте с DI проще разобраться: у него единая точка сборки, где расписаны все необходимые связи, а компоненты не зависят друг от друга напрямую.


    Проектировщиком и разработчиком системы является один-единственный человек, который (не удивительно) не ведёт подробного документирования архитектуры своего детища.

    Это соображение только усиливает предыдущий аргумент.


    У вас нет основания полагать, что на этапе проектирования Вы предусмотрели все варианты использования продукта и на этапе сдаче проекта или даже после его запуска не возникнет острой потребности в спешном порядке «допиливать» бизнес-логику; или если бизнес-среда, в которой предстоит плавать продукту, слишком изменчива, например, частые изменения регуляторов, конкуренция на рынке, отсутствие чётких стандартов/рекомендаций/практик в отрасли.

    Здесь DI позволяет получить экономию (вплоть до башни экспонент — в зависимости от объема задач) за счет:


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

    Итог: следование вашим советам — прямой убыток для бизнеса.


    Лучше один раз прочитать Симана, чем оценивать Карузо по перепевам от Рабиновича.


  1. Nikita_Danilov
    02.04.2017 09:57

    Как показал мой опыт, основная проблема бездумного использования IoC/DI-контейнеров — за ними прячут чрезмерную сложность системы. Видел системы на 5-7 уровней вложенности зависимостей. Видел по 15 интерфейсов в конструкторе. Но сам по себе IoC/DI не добавляет сложности, она рождается из конкретной реализации.
    Прятать сложность всегда опасно, надо вместо этого делать просто.


    1. Paskin
      02.04.2017 17:04
      -2

      Насколько я помню — в .NET инжекция возможна только через конструктор — из-за чего и требуется такое количество boilerplate кода. В Java же инжекция как правило происходит в загрузчике классов, не требуя ничего от «потребителя» кроме пометки соответствующих членов атрибутом Injectable. Поэтому каждый класс просто определяет, какие интерфейсы ему нужны — а сколько там еще уровней вниз ему совершенно не интересно.


      1. Nikita_Danilov
        02.04.2017 17:10
        +2

        Не, есть и Property Injection и Constructor Injection.
        Тут скорее по смыслу повелось как паттерн — в конструкторе получаем строго обязательные зависимости, в свойствах описываем опциональные.
        Однако, это не особо меняет ситуацию — 15 свойств с интерфейсами точно такое же опасное дело, точно такой же God-Object, хоть весь и на DI. Только с DI поддерживается ощущение «тут всего то параметр/свойство добавить».


        1. Bonart
          02.04.2017 22:05

          Без DI все 15 зависимостей будут закопаны в коде класса и проблему никто не увидит. Только стоимость доработок начнет расти немного быстрее экспоненты.
          Проблема не в DI (оно как раз вскрывает загнивший код) а в людях, которые считают 15 зависимостей на класс нормой.


          1. Nikita_Danilov
            02.04.2017 22:26

            Безусловно, согласен со вторым абзац. Но, если эти зависимости сложные, то при добавлении каждой новой хардкодом в классе — волей-неволей задумаешься, правильно ли ты делаешь, ведь её еще надо инициализировать правильно. Когда же имеется возможность лишь добавить свойство с нужным контрактом, то задумываться приходится явно меньше.


            1. Bonart
              03.04.2017 03:09
              +1

              Но, если эти зависимости сложные, то при добавлении каждой новой хардкодом в классе — волей-неволей задумаешься, правильно ли ты делаешь, ведь её еще надо инициализировать правильно

              Те, кому 15 зависимостей на класс норма, о правильной инициализации не задумываются.
              Вдобавок без DI сколь угодно сложная инициализация обычно выглядит гораздо проще чем есть на самом деле: достаточно "просто" вызвать конструктор зависимости с 1-4 параметрами.


              1. Paskin
                04.04.2017 14:17

                Давайте считать — порядочному компоненту нужен логгер, конфигуратор, ввод/вывод, доступ к хранилищу данных (бывает, и не одному). Так и получаются 10-15 зависимостей…


                1. mayorovp
                  04.04.2017 14:23

                  Компоненту. Но не классу.


                  1. rraderio
                    04.04.2017 15:18

                    У вас классы только с одним методом?


                    1. VolCh
                      04.04.2017 15:20
                      +2

                      Есть смысл подумать о разумности сочетания в одном классе десятка методов с разными зависимостями — часто это признак того, что единственной ответственности у класса нет.


                      1. rraderio
                        04.04.2017 15:42

                        Ну вот есть конроллер для продуктов — CRUD
                        — зависимость к хранилищу пользователей — чтобы проверить не заблокирован ли пользователь
                        — зависимость к хранилищу городов — чтобы проверить что можно сделать доставку по городу
                        — зависимость к хранилищу продуктов
                        — зависимость к хранилищу картинок
                        — зависимость к полнотекстовому поиску
                        — зависимость к емайл провайдеру
                        — зависимость к социальным сетям
                        — зависимость к логгеру


                        1. Bonart
                          04.04.2017 15:47
                          +2

                          Толстый контроллер с бизнес-логикой.
                          God object в чистом виде.


                          1. rraderio
                            04.04.2017 15:52

                            Ок, пусть будет не конроллер, пусть будет сервис (макро).
                            Т.е. вы создаете по классу CreateX, ReadX, UpdateX, DeleteX для каждой сущности?


                            1. Bonart
                              04.04.2017 16:01
                              +1

                              Нет, конечно. Выделю валидацию, нотификацию, обновление персистентного хранилища в зависимости.
                              Для множества критериев валидации сделаю компоновщик (комплексный валидатор, с зависимостями от простых).
                              И т.д. и т.п.


                              1. rraderio
                                04.04.2017 16:33
                                -1

                                Т.е. если у меня в классе 3 валидатора, то это плохо, если один, но у него внутри 3 валидатора, то это хорошо.
                                Почему это плохо, чему это мешает?


                                1. lair
                                  04.04.2017 16:43
                                  +2

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


                                  1. rraderio
                                    04.04.2017 17:24
                                    -1

                                    А это не преждевременная абстрактизация? у меня только в одном месте эти валидаторы используются

                                    вам надо его добавить везде

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


                                    1. Bonart
                                      04.04.2017 17:32

                                      Композитных валидаторов может быть несколько, если для разных целей нужны разные наборы валидаций.
                                      При этом класс композитного валидатора может быть один.


                                      1. rraderio
                                        04.04.2017 17:36

                                        Т.е. если у меня 3 валидатора то у меня будут 6 композитных валидатора, а если 4, 5 валидатора…


                                        1. Bonart
                                          04.04.2017 17:41

                                          Нет. Композитных валидаторов (объектов, класс все равно один) надо столько, сколько у вас различных вариантов его использования, а не все комбинации из простых.


                                          1. rraderio
                                            04.04.2017 17:48

                                            Ну так в приложении могут понадобиться и все 6 вариантов.


                                            1. Bonart
                                              04.04.2017 17:52

                                              Когда понадобятся — тогда и конфигурируйте их. Нет смысла делать лишнюю работу заранее.
                                              Кстати, реальный аргумент обратный — с помощью композитного валидатора можно получить любую комбинацию из простых, просто добавляя строчки кода в точку сборки.


                                              1. rraderio
                                                04.04.2017 17:57

                                                Нет смысла делать лишнюю работу заранее.

                                                Ну вот эти 3 валидатора надо использовать только в одном месте, зачем мне композитный?


                                                1. Bonart
                                                  04.04.2017 18:01
                                                  +3

                                                  Затем, что одна зависимость проще чем три.
                                                  Проще сопровождать пять классов с тремя зависимостями у каждого, чем один с пятнадцатью.


                                                  1. rraderio
                                                    05.04.2017 09:08

                                                    Можно пример, что может поменятся в зависимостях так что править изменения в одном классе сложне чем править в трех?


                                                    1. Fesor
                                                      05.04.2017 09:39

                                                      поменятся в зависимостях

                                                      тут не в зависимостях изменения надо рассматривать а в этих самых трех классах.


                                                      Короч почитайте про single responsibility principle и cohesion.


                                                      1. rraderio
                                                        05.04.2017 10:06

                                                        тут не в зависимостях изменения надо рассматривать а в этих самых трех классах.

                                                        И как эти зависимости мне будут мешать править текушии класс? Можно конкретные примеры а не только эти баззворды?


                                                        1. Fesor
                                                          05.04.2017 10:27

                                                          И как эти зависимости мне будут мешать править текушии класс?

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


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


                                                    1. Bonart
                                                      05.04.2017 10:29

                                                      Если изменение небольшое, то править придется в одном маленьком классе.
                                                      Это проще чем править в большом как с точки зрения внесения изменений, так и для оценки аффекта, контроля версий, объединения правок и всего остального.
                                                      Если изменение масштабное, то маленькие изменения в трех маленьких классах проще чем большое в одном большом классе.


                                                      1. Fesor
                                                        05.04.2017 10:37

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


                                    1. lair
                                      04.04.2017 17:38

                                      А это не преждевременная абстрактизация?

                                      Нет. Это удобство.


                                      1. rraderio
                                        04.04.2017 17:42

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


                                        1. lair
                                          04.04.2017 17:44

                                          У меня валидатор привязан к модели. Переиспользуется модель — переиспользуется и валидатор.


                                        1. Bonart
                                          04.04.2017 17:51
                                          +2

                                          Декомпозиция — метод снижения сложности.
                                          Возможность переиспользования — лишь одно из его достоинств.
                                          Отказ от декомпозиции по причине отсуствия переиспользования ведет к переусложнению на пустом месте.
                                          Композитный валидатор хорош тем, что превращает несколько зависимостей в одну, снижая сложность самого главного — класса с бизнес-логикой.
                                          Возможность переиспользования валидаторов — дополнительный бонус.


                                          1. vintage
                                            04.04.2017 22:19

                                            Любой метод, применённый бесцельно, лишь увеличивает энтропию.


                                            Огонь может согреть вас холодной ночью, а может спалить весь ваш дом.


                                  1. areht
                                    05.04.2017 00:14

                                    Допустим, у вас есть несколько контроллеров: C1, C2, C3.

                                    Они зависят от валидаторов, соответственно C1(a,b), C2(a,b,c), C3©.

                                    Мы добавляем контроллер C4 и валидатор d. Какие классы и интерфейсы в идеале должны существовать?


                                    1. lair
                                      05.04.2017 00:19
                                      +2

                                      Слишком мало информации для ответа.


                                      Я, впрочем, вообще считаю, что не должно быть прямой зависимости от валидатора, должен быть валидационные сервис, который умеет валидировать любые модели. Валидаторы привязываются к модели (в произвольном количестве).


                                      1. areht
                                        05.04.2017 01:32

                                        > Слишком мало информации для ответа.

                                        Можно додумывать

                                        > не должно быть прямой зависимости от валидатора, должен быть валидационные сервис

                                        Ну и как именно это организовано?

                                        Т.е. получаем C1, C2, C3, C4 и IValidationService c ValidationService (в котором лежит a,b,c,d)? Или a,b,c,d — отдельные(4 шт) классы-зависимости ValidationService?


                                        1. lair
                                          05.04.2017 01:33

                                          Ну и как именно это организовано?

                                          Есть валидационный сервис, который умеет читать метаданные с моделей. Валидаторы привязаны к моделям.


                                          1. areht
                                            05.04.2017 02:35

                                            А, метаданные, это ок.

                                            Меня заинтриговала именно логика композиции в «А если вы добавляете валидатор в композитный валидатор, то править надо только композитный». Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?


                                            1. mayorovp
                                              05.04.2017 09:38

                                              Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?

                                              Для начала — слезть с MVC-подобного фреймворка.


                                              1. areht
                                                05.04.2017 09:49
                                                -1

                                                Еще раз, вопрос не про выбор фреймворка, а про композицию классов.


                                              1. rraderio
                                                05.04.2017 10:23

                                                Слезть на что?


                                            1. lair
                                              05.04.2017 13:31
                                              +2

                                              Давайте для простоты представим, что Главный Архитектор ненавидит метаданные и запретил. Тогда как поступить?

                                              Лучше всего — уйти из-под такого Главного Архитектора. Но если этот вариант недоступен, то руками написать по валидатору на каждую модель (и каждый такой валидатор вполне себе может быть композитным, потому что у разных моделей есть свойство Email, которое валидируется одинаково выделенным валидатором), и руками же делать их выбор в зависимости от того, какую модель мы валидируем.


                                              1. areht
                                                05.04.2017 19:47

                                                То есть у нас на сложной модели будет один композитный валидатор, скажем, с полусотней методов и двумя десятками зависимостей?


                                                1. Bonart
                                                  05.04.2017 19:54

                                                  С чего вдруг? У композитного валидатора на входе типизированный IEnumerable с простыми валидаторами, и возвращает он сводный список результатов валидации.


                                                  1. areht
                                                    05.04.2017 21:25

                                                    Как я понял, lair композитным валидатором назвал другое.

                                                    Ещё раз, у нас есть контроллер, он должен валидировать. У каждого поля есть свои правила валидации. Т.е. нам нужна связка поля и правила. Их много.

                                                    Эти связки не должны лежать в контроллерах. Они не лежат и в EmailFormatValidator. Значит за них отвечает (композитный) валидационный сервис. Тогда он будет весьма жирненьким.


                                                    1. vintage
                                                      05.04.2017 21:27

                                                      Эти связки не должны лежать в контроллерах.

                                                      Почему?


                                                    1. Bonart
                                                      06.04.2017 02:49

                                                      Ещё раз, у нас есть контроллер, он должен валидировать. У каждого поля есть свои правила валидации. Т.е. нам нужна связка поля и правила. Их много.

                                                      На мой взгляд предположение о том, что валидация сводится к отдельным полям, неверно.
                                                      Проверяется сущность в целом и результат в общем случае зависит от сочетания значения всех полей. Простые валидаторы не обязаны проверять какое-то одно поле — они делают какую-то одну атомарную проверку, которая может включать как одно, так и несколько полей, и это — детали реализации конкретного валидатора.
                                                      Разумеется, ряд диагностик (проверка адреса электронной почты и т.п.) можно выделить и переиспользовать дополнительно, но я не стал бы вязаться к полю: все равно валидируется сущность в целом (и этой сущностью может быть строка с мылом).


                                                      Тогда он будет весьма жирненьким.

                                                      Жирненькие классы и методы предпочитаю предотвращать, на худой конец устранять, но не пытаться подводить под них обоснование ничего не делать.


                                                      1. areht
                                                        06.04.2017 03:06

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

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

                                                        > Жирненькие классы и методы предпочитаю предотвращать, на худой конец устранять, но не пытаться подводить под них обоснование ничего не делать.

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


                                                        1. Bonart
                                                          06.04.2017 14:08

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


                                                          1. areht
                                                            06.04.2017 15:07

                                                            > за счет отказа от внешней связи

                                                            Вы забыли написать куда эта связь переноситься.


                                                            1. Bonart
                                                              06.04.2017 17:17

                                                              Я не забыл, пожалуйста, читайте внимательнее:


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

                                                              Атомарный валидатор получает в качестве параметра сущность целиком и сам знает, какие поля проверять.


                                                              1. areht
                                                                06.04.2017 21:19

                                                                Без последнего абзаца совершенно непонятно вызываем мы как Check(object) или Check(object, o => o.Name)

                                                                Т.е. если нам надо проверить сотню полей на «не пустая строка» — мы должны описать сотню классов атомарных валидаторов?


                                                                1. Bonart
                                                                  07.04.2017 10:51

                                                                  Без последнего абзаца совершенно непонятно вызываем мы как Check(object) или Check(object, o => o.Name)

                                                                  Второй вариант делает связь валидатора с полем внешней, что прямо противоречит моему предложению.


                                                                  Т.е. если нам надо проверить сотню полей на «не пустая строка» — мы должны описать сотню классов атомарных валидаторов?

                                                                  Зачем это перепроектирование? На пустоту 100 полей спокойно проверит один атомарный валидатор.


                                                                  1. areht
                                                                    07.04.2017 11:16

                                                                    Один атомарный валидатор проверяет вообще все сущности в солюшене, или вы их по какому-то принципу делите?


                                                                    1. Bonart
                                                                      07.04.2017 11:52

                                                                      Интерфейс валидатора — обобщенный, параметризованный типом сущности.


                                                                      1. areht
                                                                        07.04.2017 12:25

                                                                        А это чем-то помогает понять какие поля надо проверять на «не пустое»?


                                                                        1. Bonart
                                                                          07.04.2017 12:58

                                                                          А это зачем-то надо понимать не из кода валидатора?


                                                                          1. areht
                                                                            07.04.2017 13:26

                                                                            Ещё раз, валидатор у вас всё же один, но вы его зачем-то, непонятно зачем, параметризуете. Зачем?


                                                                            1. Bonart
                                                                              07.04.2017 15:04

                                                                              С чего вы взяли, что валидатор у меня должен быть один?
                                                                              Имея обобщенный интерфейс валидатора, его реализации можно выбирать по вкусу — от универсальной со сканированием типов полей и метаданных до специализированной на конкретный тип сущности.


                                                                              1. areht
                                                                                07.04.2017 15:21
                                                                                -1

                                                                                > С чего вы взяли, что валидатор у меня должен быть один?

                                                                                Будет проще, если вы перестанете отвечать вопросом на вопрос. Начать можно с

                                                                                > Один атомарный валидатор проверяет вообще все сущности в солюшене, или вы их по какому-то принципу делите?

                                                                                Ну и вообще перестать отвечать «можно выбирать по вкусу» (спасибо, я в курсе).


                                                                                1. Bonart
                                                                                  08.04.2017 04:22

                                                                                  Пожалуйста, читайте внимательнее.
                                                                                  Ответ на ваш вопрос про атомарные валидаторы уже есть:


                                                                                  Интерфейс валидатора — обобщенный, параметризованный типом сущности.

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


                                                                                  1. areht
                                                                                    08.04.2017 04:43

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


                                                                                    1. Fesor
                                                                                      08.04.2017 15:26

                                                                                      Но именно в этом смысл, как именно вы затем реализуете ваш этот валидатор — дело лично ваше. Суть в том что благодаря этому единому интерфейсу уровень связанности будет небольшой.


                                                                                      1. areht
                                                                                        09.04.2017 01:23

                                                                                        > Но именно в этом смысл, как именно вы затем реализуете ваш этот валидатор — дело лично ваше.

                                                                                        Смысл чего? Мой вопрос был про вторую стадию, когда «дело лично ваше».

                                                                                        1) Делаем единый интерфейс
                                                                                        2) ???
                                                                                        3) Profit!

                                                                                        > Суть в том что благодаря этому единому интерфейсу уровень связанности будет небольшой.

                                                                                        Раз уж на мой вопрос никто не хочет отвечать… Он предлагает не «единый», а «обобщенный». То есть для каждого типа он на самом деле свой. Уровень связности он даст не меньше любого другого интерфейса, а то и прибывит. Например в DI придётся регистрировать отдельно для каждого наследника в иерархии.


                                                                                        1. Fesor
                                                                                          09.04.2017 12:57

                                                                                          Мой вопрос был про вторую стадию, когда «дело лично ваше».

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


                                                                                          Он предлагает не «единый», а «обобщенный»

                                                                                          а вы можете объяснить разницу в контексте нахождения абстракции для валидатора?


                                                                                          То есть для каждого типа он на самом деле свой.

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


                                                                                          Уровень связности он даст не меньше любого другого интерфейса

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


                                                                                          Например в DI придётся регистрировать отдельно для каждого наследника в иерархии.

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


                                                                                          1. areht
                                                                                            09.04.2017 14:06

                                                                                            > Модуль который использует валидаторы теперь знает только об одном типе.

                                                                                            Давайте вот с этого места поподробнее. Мне приходит абстрактный родитель P (у которого есть наследники A и B) который надо отвалидировать (соответственно валидируются ValidatorA:IValidator<A> и ValidatorB:IValidator<B>). Можете показать как выглядит полиморфизм в коде использующего их модуля?

                                                                                            > DI никак не влияет на уровень связанности

                                                                                            Если у вас есть код регистрации — влияет.


                                                                                            1. Fesor
                                                                                              09.04.2017 16:32

                                                                                              Можете показать как выглядит полиморфизм в коде использующего их модуля?

                                                                                              validator.validate<MyEntity>(entity)

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


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


                                                                                              Если у вас есть код регистрации — влияет.

                                                                                              не влияет, это отдельный модуль.


                                                                                              1. areht
                                                                                                09.04.2017 20:54

                                                                                                У вас MyEntity — это P, A или B? Или Object?


                                                                                                1. Fesor
                                                                                                  09.04.2017 21:03

                                                                                                  У меня нет P, A или B. У меня есть клиентский код которому надо провалидировать объект с типом MyEntity и он делигирует эту работу некому валидатору. Этот валидатор затем достанет для нашего типа другой валидатор, который может быть композицией других валидаторов которые будут непосредственно валидировать поля сущности.


                                                                                                  И все. Клиенсткому коду больше ничего про валидацию знать не надо кроме "это не твое дело". Правила валидации будут описаны в другом модуле.


                                                                                                  1. areht
                                                                                                    09.04.2017 22:07

                                                                                                    > У меня нет P, A или B. У меня есть клиентский код которому надо провалидировать объект с типом MyEntity

                                                                                                    Рад за Вас! К сожалению, я полиморфизм использую на объектах разных типов, например наследниках.


                                                                                                    1. Fesor
                                                                                                      09.04.2017 23:49

                                                                                                      я полиморфизм использую на объектах разных типов

                                                                                                      разные типы — нет полиморфизма. Либо поясните.


                                                                                                      1. areht
                                                                                                        10.04.2017 06:51

                                                                                                        Внезапно.

                                                                                                        > В языках программирования и теории типов полиморфизмом называется способность функции обрабатывать данные разных типов

                                                                                                        https://ru.wikipedia.org/wiki/Полиморфизм_(информатика)


                                                                                                    1. qw1
                                                                                                      10.04.2017 12:37
                                                                                                      -1

                                                                                                      К сожалению, я полиморфизм использую на объектах разных типов, например наследниках.
                                                                                                      «К сожалению», потому что понимаете, что этим нарушаете принцип 'L' из SOLID?

                                                                                                      Если у вас есть код
                                                                                                      void DoSomethng(P entity)
                                                                                                      {
                                                                                                          validator.Validate<P>(entity);
                                                                                                      }

                                                                                                      Значит, этим вы показываете, что свойства наследников, отпочкованных от P, здесь вам не нужны. Валидировать объект надо как P, а не как A или B.

                                                                                                      Если всё же разница есть, язык достаточно мощный, чтобы её выразить:
                                                                                                      void DoSomethng<T>(T entity) where T : P
                                                                                                      {
                                                                                                          validator.Validate<T>(entity);
                                                                                                      }



                                                                                                      1. areht
                                                                                                        10.04.2017 13:13
                                                                                                        -1

                                                                                                        > Значит, этим вы показываете, что свойства наследников, отпочкованных от P, здесь вам не нужны. Валидировать объект надо как P, а не как A или B.

                                                                                                        Я понимаю, что предложить прочитать ТЗ — это верх наглости, но…

                                                                                                        > Мне приходит абстрактный родитель P

                                                                                                        > соответственно валидируются ValidatorA:IValidator<A> и ValidatorB:IValidator<B>

                                                                                                        То есть интересующий меня код выглядит примерно так

                                                                                                        void DoSomethng(P entity)
                                                                                                        {
                                                                                                            validator.Validate<???>(entity);
                                                                                                        }
                                                                                                        


                                                                                                        > Валидировать объект надо как P, а не как A или B.

                                                                                                        А вот это нарушает L.


                                                                                                        1. qw1
                                                                                                          10.04.2017 14:36
                                                                                                          -1

                                                                                                          А вот это нарушает L.
                                                                                                          Может, у меня другое понимание этого принципа. Если бы P имел виртуальный метод валидации, а наследник его перекрывал, то можно было бы безболезненно передавать наследника в метод, потому что метод ничего не знает об отличии A от P:
                                                                                                          entity.Validate() — нет проблем с LSP.


                                                                                                          У вас же метод *должен* знать об отличии P от A (о чём и говорит "???"), а это нарушение LSP.

                                                                                                          Я сейчас не пытаюсь доказать, что SOLID это хорошо и догма, и только по нему надо писать. Но факт есть факт. Метод с
                                                                                                          validator.Validate<???>(entity)
                                                                                                          нарушает LSP.


                                                                                                          1. qw1
                                                                                                            10.04.2017 14:44

                                                                                                            Я себя поправлю. Код

                                                                                                            void DoSomethng(P entity)
                                                                                                            {
                                                                                                                validator.Validate<P>(entity);
                                                                                                            }

                                                                                                            нарушает LSP (о чём я и написал в своём первом комменте).

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


                                                                                                          1. areht
                                                                                                            10.04.2017 14:51

                                                                                                            «Нарушает» не совсем точно. Для разговора о LSP надо определить функцию (валидатор) над P. А я определил 2 валидатора — для A и B. И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate().

                                                                                                            > Но факт есть факт. Метод с validator.Validate<???>(entity) нарушает LSP.

                                                                                                            Это да. Но дженерик в валидаторе то придумал не я.


                                                                                                            1. Fesor
                                                                                                              10.04.2017 16:28

                                                                                                              То есть реализация паттерна стратегия нарушает LSP?


                                                                                                              А я определил 2 валидатора — для A и B. И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate().

                                                                                                              Вот только теперь entity знает о валидации и слегка так нарушает SRP. Да и зачем валидировать сущность если она и так должна быть валидной всегда.


                                                                                                              1. areht
                                                                                                                10.04.2017 17:09

                                                                                                                > Вот только теперь entity знает о валидации и слегка так нарушает SRP.

                                                                                                                > То есть реализация паттерна стратегия нарушает LSP?

                                                                                                                И SRP, видимо.

                                                                                                                > Да и зачем валидировать сущность если она и так должна быть валидной всегда.

                                                                                                                Глубокая мысль, запишу.


                                                                                                            1. qw1
                                                                                                              10.04.2017 17:37
                                                                                                              -1

                                                                                                              «Нарушает» не совсем точно
                                                                                                              Я руководствовался таким принципом:

                                                                                                              Если фунция принимает P, она не должна опираться на свойства, не входящие в контракт P

                                                                                                              То есть корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода валидации.
                                                                                                              И это вполне SOLID валидаторы, которые могут вызываться и из entity.Validate()
                                                                                                              А тут уже нарушается SRP. Entity отдельно, валидация отдельно.


                                                                                                              1. areht
                                                                                                                10.04.2017 18:20
                                                                                                                +1

                                                                                                                > Если фунция принимает P, она не должна опираться на свойства, не входящие в контракт P

                                                                                                                Это LSP или вообще? )

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

                                                                                                                > То есть корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода валидации.

                                                                                                                У нас в контракт P как-бы входит кастинг

                                                                                                                (entity as IValidatable)?.Validate() ?? true
                                                                                                                


                                                                                                                А с т.з. LSP принципиально, что мы не упоминаем ни A, ни B, ни С, и код будет работать (если ABC сами не нарушат LSP).

                                                                                                                А каким определением SRP вы руководствуетесь?


                                                                                                                1. qw1
                                                                                                                  10.04.2017 19:23

                                                                                                                  >> А каким определением SRP вы руководствуетесь?

                                                                                                                  Определения равнозначны.
                                                                                                                  Если в контракт входит возможный кастинг к IValidatable, то можно валидировать A,B,C, не нарушая SRP. Иначе, как и писал раньше

                                                                                                                  корректно (с т.з. LSP) сделать валидацию невозможно, если принимать только базовый класс, в контракте которого нет метода способа валидации.


                                                                                                    1. Bonart
                                                                                                      10.04.2017 19:23
                                                                                                      +1

                                                                                                      А полиморфизм бывает еще и параметрический. Именно его обобщенные типы и реализуют.


                                                                                                      1. areht
                                                                                                        10.04.2017 19:36

                                                                                                        Это замечательно. А с наследованием этот ваш параметрический полиморфизм совместим?

                                                                                                        А можно посмотреть? А то я никак не пойму какую же именно строчку кода вы полиморфизмом все называете.



                                                                                                        1. Bonart
                                                                                                          10.04.2017 19:48

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


                                                                                                          1. areht
                                                                                                            10.04.2017 22:39

                                                                                                            То есть параметрический полиморфизм, по-вашему, заключается в том, что я могу интерфейс реализовать несколькими способами? А к исполняемому коду отношения не имеет?


                                                                                              1. Bonart
                                                                                                10.04.2017 19:21
                                                                                                +2

                                                                                                Нет-нет-нет, Дэвид Блейн!
                                                                                                Обобщенным должен быть интерфейс, а не отдельный метод.


                                                                                                public interface IValidator<in T>
                                                                                                {
                                                                                                   IEnumerable<ValidationResult> Validate(T entity);
                                                                                                }

                                                                                                Обобщенный метод такого плана — разновидность ServiceLocator, ну его нафиг.


                                                                                                1. qw1
                                                                                                  10.04.2017 19:45
                                                                                                  -1

                                                                                                  Отличное замечание!
                                                                                                  Тогда реализуем вот так:

                                                                                                          public IValidator<int> IntValidator { get; set; }
                                                                                                  
                                                                                                          public IValidator<string> StringValidator { get; set; }
                                                                                                  
                                                                                                          public void DoSomethng<T>(T entity, IValidator<T> validator)
                                                                                                          {
                                                                                                              validator.Validate(entity);
                                                                                                          }
                                                                                                  
                                                                                                          public void Caller()
                                                                                                          {
                                                                                                              int x = 10;
                                                                                                              DoSomethng(x, IntValidator);
                                                                                                              string s = "yes";
                                                                                                              DoSomethng(s, StringValidator);
                                                                                                          }
                                                                                                  


                                                                                                  1. areht
                                                                                                    11.04.2017 05:47

                                                                                                    Остался один шаг до DoAnything(lambda, params[])

                                                                                                    Но нет, публичное API менять по-прежнему не будем.


                                                                                                    1. qw1
                                                                                                      11.04.2017 09:18

                                                                                                      Предложите удачный вариант нового API


                                                                                                      1. areht
                                                                                                        11.04.2017 09:43
                                                                                                        -2

                                                                                                        API ровно одно, public void DoSomethng(P entity). И с этим надо жить.

                                                                                                        Что внутри делать — это другой вопрос.

                                                                                                        По валидации я знаю 2 не очень плохих варианта — метаданные и метод на сущности. Хороших — не знаю. Дженерик — плохой.


                                                                                                        1. Bonart
                                                                                                          11.04.2017 09:53
                                                                                                          +1

                                                                                                          В результате у ваших классов и методов третья стадия ожирения.


                                                                                                          1. areht
                                                                                                            11.04.2017 11:05

                                                                                                            Так вы покажите свои, а мы посмотрим у кого жирнее.


                                                                                                            1. Bonart
                                                                                                              11.04.2017 11:18

                                                                                                              Смотрите на здоровье:
                                                                                                              https://bitbucket.org/KirillMaurin/fluenthelium


                                                                                                              1. areht
                                                                                                                11.04.2017 12:05
                                                                                                                -2

                                                                                                                То есть практического опыта применения магического интерфейса с дженериком нет, понятно.


                                                                                                        1. qw1
                                                                                                          11.04.2017 16:24

                                                                                                          public void DoSomethng(P entity).
                                                                                                          Что внутри делать — это другой вопрос.
                                                                                                          Ну ок, обменялись плохими практиками )))


                                                1. lair
                                                  06.04.2017 11:51

                                                  Не совсем.


                                                  Во-первых, (публичный) метод будет один (и он не будет зависеть от сложности модели).


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


                        1. mayorovp
                          04.04.2017 15:56

                          Вы имеете в виду веб-контроллер? Это пограничный класс, который всегда создается инфраструктурой и никогда программистом. И позднее связывание контроллеров — врожденная проблема многих веб-фреймворков, DI с IoC тут уже ничего не портят, ибо некуда.


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


                          • проверка не заблокирован ли пользователь должна выполняться общим решением по авторизации, а не boilerplate-кодом в каждой операции контроллера, а потому зависимость к хранилищу пользователей — мимо;


                          • как вы собрались проверять доставку по городу через хранилище городов? Запрашивая информацию в цикле что ли? Если нужно отдавать ответ быстро — этим может заниматься только хранилище продуктов;


                          • для того чтобы сформировать ссылку на картинку обычно не требуется доступ к хранилищу картинок;


                          • зачем тут нужны соц. сети и email провайдер — даже не представляю...


                          1. rraderio
                            04.04.2017 16:39

                            как вы собрались проверять доставку по городу через хранилище городов?

                            когда показываем продукт, смотрим если он в Москве, то показываем пользователю что можем доставить

                            для того чтобы сформировать ссылку на картинку обычно не требуется доступ к хранилищу картинок

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

                            зачем тут нужны соц. сети и email провайдер — даже не представляю

                            пользователь может поставить галочку опубликовать в соц. сети


                            1. mayorovp
                              04.04.2017 16:42

                              когда показываем продукт, смотрим если он в Москве, то показываем пользователю что можем доставить

                              А оптимизировать запросы к БД не пробовали?


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

                              А зачем ее хранить?


                              1. rraderio
                                04.04.2017 16:56

                                А зачем ее хранить?

                                А как вы показываете картинки продукта?


                                1. mayorovp
                                  04.04.2017 16:57

                                  Отправляю тэг с ссылкой, по которой можно картинку получить. А что, это как-то по-другому делается?..


                                  1. rraderio
                                    04.04.2017 17:04

                                    Ок, в тэги есть ссылка, откуда вы ее берете?


                                    1. mayorovp
                                      04.04.2017 17:05

                                      Генерирую в соответствии со схемой


                                      1. rraderio
                                        04.04.2017 17:17

                                        Т.е. у вас у картинки продукта всегда одна и таже ссылка?
                                        Т.е. вы никогда не кэшируете картинки?


                    1. mayorovp
                      04.04.2017 15:21

                      А при чем тут количество методов в классе?


                      1. rraderio
                        04.04.2017 15:35

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


                        1. mayorovp
                          04.04.2017 15:38
                          +1

                          Обычно разные зависимости у разных методов — это сигнал что что-то не так с архитектурой.


                          1. rraderio
                            04.04.2017 15:47

                            При добавлении продукта мне нужны зависимости к
                            — зависимость к хранилищу пользователей — чтобы проверить не заблокирован ли пользователь
                            — зависимость к хранилищу городов — чтобы проверить что можно сделать доставку по городу
                            — зависимость к хранилищу продуктов
                            — зависимость к хранилищу картинок
                            — зависимость к емайл провайдеру
                            — зависимость к социальным сетям
                            — зависимость к логгеру
                            При извлечении продукта мне нужны зависимости к
                            — зависимость к хранилищу продуктов
                            — зависимость к хранилищу картинок
                            — зависимость к полнотекстовому поиску
                            — зависимость к логгеру

                            Предлагаете по одно классу на метод?


                            1. Fesor
                              04.04.2017 15:53

                              зависимость к хранилищу пользователей — чтобы проверить не заблокирован ли пользователь

                              проверяем это на уровень выше, декорация. Инкапсулируем эту проверку в один объект который знает как это проверять и не пускает дальше если све плохо.


                              зависимость к хранилищу городов — чтобы проверить что можно сделать доставку по городу

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


                              зависимость к емайл провайдеру

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


                              зависимость к хранилищу картинок

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


                              зависимость к социальным сетям

                              А это откуда вылезло при простом добавлении продуктов?


                              зависимость к логгеру

                              cross-cutting concerns можно выносить в декораторы или делать аспекты (АОП).


                              зависимость к полнотекстовому поиску

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


                              Предлагаете по одно классу на метод?

                              В этом случае скорее да чем нет.


                              1. rraderio
                                04.04.2017 16:50

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

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

                                к которому у нас тоже зависимость
                                А все картинки уже залиты.

                                А связи между картинками и продуктом, или у вас ссылки на картинки хранятся в той же таблице?
                                А это откуда вылезло при простом добавлении продуктов?

                                автоматически шарить с соц. сети


                                1. Fesor
                                  04.04.2017 17:00

                                  нам надо показать, можем мы доставить или нет

                                  ну так ловите эксепшен на уровне выше аля DeliveryNotSupportedForCountry и вуаля.


                                  к которому у нас тоже зависимость

                                  но про зависимости зависимостей мы не должны париться ибо Law of demeter. Инкапсуляция и все такое.


                                  А связи между картинками и продуктом, или у вас ссылки на картинки хранятся в той же таблице?

                                  у меня за этим следит ORM. ну или Table Gateway какой-то мэпит объект со связями на таблички. Это уже как сделаете. Ну и да, на нескольких проектах я тупо храню ссылки на картинки в jsonb.


                                  автоматически шарить с соц. сети

                                  опять же декорация, доменные ивенты.


                            1. mayorovp
                              04.04.2017 15:57

                              Зачем вы пишите одно и то же в двух ветках? Ответил вам выше.


                        1. Fesor
                          04.04.2017 15:48

                          тогда у вас не кохизив класс и высокая связанность.


                          1. rraderio
                            04.04.2017 15:58
                            -1

                            высокая связанность

                            между кем? Я всегда могу вынести любой метод в другой класс


                            1. Fesor
                              04.04.2017 16:07

                              между кем?

                              у нас есть класс. У него несколько методов. допустим 3. У каждого метода по две зависимости. Итого 6 зависимостей на один класс. Итого — высокая связанность.


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


                              Как следствие все будет хорошо и с SRP и с ISP. Будет проще находить какой код править. Будет проще менять код.


                              1. rraderio
                                04.04.2017 16:44

                                Итого 6 зависимостей на один класс. Итого — высокая связанность.

                                А сколько зависимостей должно быть чтобы не было высокой связанности?


                                1. Fesor
                                  05.04.2017 00:15
                                  +1

                                  Чем меньше — тем лучше. 0 недостижим. Рекомендуемый максимум — 3 зависимости на класс. Если выходит больше — стоит хотя бы лишние пару минут подумать. Иногда настолько загоняться становится банально не выгодно. Но в целом высокое количество зависимостей плохо говорит о декомпозиции задачи. На маленьких пруф-оф-концепт реализациях можно не загоняться конечно но все мы знаем что сегодня прототип завтра продакшен.


                                  1. babylon
                                    05.04.2017 19:24

                                    3 зависимости. Ага. Это если вы сами всё разрабатываете. Их бывает штук 20 в классе. И абстрактных методов и инстансов. В этом случае агрегатирующий их класс выполняет функцию бандла. Собственно для обсуждаемого сабжа количество зависимостей не принципиально. Они есть и с этим надо смириться. Иначе вы попросту не достигните требуемого функционала.
                                    Интерфейс влияет только на правила взаимодействия. Определяя или переопределяя их.


                                    1. Bonart
                                      05.04.2017 19:55
                                      +3

                                      Ну вы смиряйтесь если хотите. А я такое не стесняюсь рефакторить.


                                  1. rraderio
                                    06.04.2017 11:27

                                    Если в методе сервиса надо сделать 4 запроса в 4 таблицы(4 зависимости на репозитории), как тут быть?


                                    1. VolCh
                                      06.04.2017 11:36

                                      Совсем-совсем несвязанные 4 запроса к разным базам?


                                      1. rraderio
                                        06.04.2017 11:36

                                        Да


                                        1. lair
                                          06.04.2017 11:53
                                          +4

                                          Это выглядит так, как если бы ваш "метод сервиса" нарушал принцип единой ответственности.


                                          1. rraderio
                                            06.04.2017 12:12

                                            Мне на главной странице надо показать, погоду, курс валют, продукты, есть ли у пользователя новые сообщения.


                                            1. lair
                                              06.04.2017 12:13
                                              +1

                                              Так это совершенно разные сервисы же


                                              1. rraderio
                                                06.04.2017 12:17

                                                Ну кто-то их же вызывает в одном методе, т.е. у него на них зависимости


                                                1. lair
                                                  06.04.2017 12:21

                                                  Во-первых, я не вижу необходимости вызывать это в одном методе.


                                                  Во-вторых, такая вещь абстрагируется до "компонент первой страницы", и тогда у вас остается одна зависимость — перечисление компонентов первой страницы.


                                                  1. rraderio
                                                    06.04.2017 12:23
                                                    -1

                                                    Во-первых, я не вижу необходимости вызывать это в одном методе.

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

                                                    И у этого «компонент первой страницы» 4 зависимости, да?


                                                    1. lair
                                                      06.04.2017 12:26
                                                      +1

                                                      А как? Есть запрос отобразить гланую страницу

                                                      Он не обязан вызывать все сервисы, он может просто ответить, какие компоненты нужны.


                                                      И у этого «компонент первой страницы» 4 зависимости, да?

                                                      Нет. Погода — компонент, курс валют — компонент, продукты — компонент, и новые сообщения — компонент.


                                                      1. rraderio
                                                        06.04.2017 12:35
                                                        -1

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


                                                        1. lair
                                                          06.04.2017 12:36

                                                          Четыре запроса на сервер вы сам придумали. В моем же предложении все происходит на сервере, не покидая его.


                                                          1. rraderio
                                                            06.04.2017 12:40

                                                            Он не обязан вызывать все сервисы, он может просто ответить, какие компоненты нужны.

                                                            Ок, он знает какие компоненты нужны, но как он получит информацию если он не будет их вызывать их на сервере?


                                                            1. lair
                                                              06.04.2017 12:42
                                                              +1

                                                              Говорю же: если у компонент единый интерфейс, вместо n зависимостей вы получаете одну зависимость (на перечисление из n элементов). Для потребителя они все обрабатываются одинаково.


                                                              1. rraderio
                                                                06.04.2017 12:45
                                                                -1

                                                                если у компонент единый интерфейс

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


                                                                1. lair
                                                                  06.04.2017 12:49
                                                                  +2

                                                                  А вот тут уже вы додумали.

                                                                  Я не додумал, я сказал, что его надо ввести.


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

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


                                                                  Иначе говоря, представьте себе главную страницу, которая отрисовывает только курс валют. Есть параметры у этого запроса? Нет. Повторяем аналогичное упражнение для всех четырех ваших компонентов, и видим, что у них как раз единый интерфейс — без каких-либо параметров.


                                                                  1. rraderio
                                                                    06.04.2017 13:58

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

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


                                                                    1. lair
                                                                      06.04.2017 14:01

                                                                      Т.е. теперь сервисы знают об слое запроса, это ок?

                                                                      Не больше, чем они знали раньше.


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

                                                                      Брать дату из БД, простите, неумно. И такой запрос, в любом случае, оптимизируется.


                                                                      1. rraderio
                                                                        06.04.2017 14:09

                                                                        Не больше, чем они знали раньше.

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

                                                                        В зависимости от зоны пользователя надо показывать разные даты


                                                                        1. lair
                                                                          06.04.2017 14:12

                                                                          Больше, у меня сервисы как отдельная билиотека, отдельный слой, которая ничего не знает о запросе

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


                                                                          В зависимости от зоны пользователя надо показывать разные даты

                                                                          Значит, из БД надо брать зону пользователя (а не дату). Это все равно профиль пользователя, который надо загружать один раз на (HTTP-)запрос.


                                                        1. lair
                                                          06.04.2017 12:58
                                                          +1

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


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


                                                          Во-вторых, у них разная кэшируемость.


                                                          1. VolCh
                                                            06.04.2017 13:18
                                                            +1

                                                            В третьих, если один не отработает почему-то, то остальные три отработают


                                                            1. vintage
                                                              06.04.2017 13:35

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


                                                              1. VolCh
                                                                06.04.2017 13:56

                                                                Это надо постараться надо так написать, по-моему.


                                                                1. vintage
                                                                  06.04.2017 14:06

                                                                  Типичный код:


                                                                  Promise.all([ getOne(), getTwo(), getThree() ]).then( ... ).catch( ... )

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


                                                              1. lair
                                                                06.04.2017 14:01

                                                                … плохо написанное. Не делайте так.


                                                                1. vintage
                                                                  06.04.2017 14:18
                                                                  -1

                                                                  Я так и не делаю. Более того, я запилил фреймворк, который позволяет делать хорошо без лишних сложностей, но никто не хочет им пользоваться, предпочитая всякие ui-router-ы, которые показываются страницу лишь после загрузки всех данных.


                                                          1. rraderio
                                                            06.04.2017 14:04
                                                            -1

                                                            могут быть лучше

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

                                                            Мне не трудно сделать веб сервис для этого отдельно
                                                            Во-вторых, у них разная кэшируемость.

                                                            Можно кэшировать на сервере


                                                            1. lair
                                                              06.04.2017 14:05

                                                              Мне не трудно сделать веб сервис для этого отдельно

                                                              Отдельно для чего?


                                                              Можно кэшировать на сервере

                                                              Это все равно хуже, чем закэшировать на проксе по времени.


                                                              1. rraderio
                                                                06.04.2017 14:14

                                                                Отдельно для чего?

                                                                Отдельный веб сервис для календаря, отдельный веб сервис для курса валют, если такие сервисы нужны


                                                                1. lair
                                                                  06.04.2017 14:15

                                                                  Вот мы и пришли к тому, что не надо четырех зависимостей в методе главной страницы. О чем сразу речь и шла.


                                                                  1. rraderio
                                                                    06.04.2017 14:21

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


                                                                    1. lair
                                                                      06.04.2017 14:22
                                                                      +1

                                                                      … или не надо, потому что см. выше.


                                                                      Или если очень надо, то вам уже показали, как это декомпоновать.


                    1. Fesor
                      04.04.2017 15:47

                      у меня как правило — да. А если нет — значит имплементят несколько интерфейсов. Есть конечно исключения, где с точки зрения кохижен выгодно.


  1. Paskin
    02.04.2017 17:07

    А как получилось изменить сигнатуру метода, не меняя имплементированный интерфейс?


  1. rraderio
    03.04.2017 10:57
    -1

    Тем кто использует DI, а что вы делаете если надо поменять DI контайнер, он у вас тоже в DI?


    1. Bonart
      03.04.2017 11:33
      +1

      DI-контейнер — инструмент для облегчения реализации точки сборки (Composition Root), в обычных классах на него ссылок нет.
      Как результат для замены DI-контейнера необходимо и достаточно переписать точку сборки с использованием нового инструмента вместо старого.


      1. rraderio
        03.04.2017 12:58

        А как вы инжектите зависимости?


        1. Bonart
          03.04.2017 13:05

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


          1. rraderio
            03.04.2017 14:24

            Реализации вы регистрируете вручную или аннотоациями?


            1. Bonart
              03.04.2017 14:46
              +1

              Обычный класс ничего не знает о точке сборки, включая аннотации, поэтому аннотации в топку — они навязывают классу паразитную зависимость от конкретных вариантов использования.
              Вот только аннотации — не единственный способ автоматизации маркировки зависимостей и регистрации реализаций.
              Практически все контейнеры автоматически трактуют параметры конструктора как зависимости без необходимости в аннотациях.
              Большинство контейнеров (кроме откровенного легаси вроде Spring.NET) позволяют регистрировать зависимости прямо в коде, что означает возможность использования любой автоматизации при построении точки сборки.
              На практике чаще всего используются конвенции (Autofac умеет разрешать делегаты, имея только регистрацию возвращаемого типа), сканирование сборок и генерация регистраций из конфигурационных файлов.
              Я предпочитаю вариант регистрация в коде, дополненную поддержкой конвенций.
              Это позволяет сочетать ясность, краткость и гибкость в коде точки сборки.


              1. rraderio
                03.04.2017 15:29

                Т.е. у одного интерфейса только одна реализация зарегистрирована?


                1. Bonart
                  03.04.2017 15:50

                  Нет, конечно, единственность реализации не обязательна.


                  1. rraderio
                    03.04.2017 15:58

                    Хм, а откуда тогда контейнер знает какую реализацию надо внедрить?


                    1. Bonart
                      03.04.2017 16:32

                      Обычно имеется умолчание: используется последняя регистрация для данного интерфейса.
                      Если нужно перебрать все реализации — используется соглашение, при котором контенер разрешает IEnumerable как все зарегистрированные реализации T
                      Если нужно указать конкретную реализацию — используется регистрация по ключу или по имени.
                      Если нужно разрешать по-разному в разных контекстах — используются дочерние точки сборки (по одной на контекст).
                      Способов вагон и маленькая тележка — можно использовать любой готовый или реализовать свой.


  1. lair
    03.04.2017 13:58
    +4

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

    Вообще-то, оба этих паттерна никак не влияют на распределение логики (кроме логики вызова/инстанциирования). DI влияет только на то, как именно вы связываете уже существующие куски логики.


    Очень информативно, согласитесь?

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


    Так и IoC/DI идут рука об руку вместе с шаблоном Service Locator

    Нет, не идут. Dependency Injection — это прямая противоположность Service Locator. То же самое относится и к IoC — когда вы используете Service Locator, вы не используете IoC, у вас "прямое" управление.


    Service Locator, который тесно связан с идеей позднего связывания. При этом при компиляции решения не проверяется, соответствует ли сигнатура методов требованиям в точке вызова.

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


    Вы не уверены на 100%, что на этапе запуска проекта, его стабилизации, опытной и даже промышленной эксплуатации будут оперативно доступны разработчики этого продукта.

    Это ошибка. DI (как и IoC) — это общеизвестный паттерн, не привязанный к одному продукту. Более того, я вот когда получаю в наследство код, в котором все зависимости прямые, обычно очень хочу как-нибудь отомстить его автору.


    Проектировщиком и разработчиком системы является один-единственный человек, который (не удивительно) не ведёт подробного документирования архитектуры своего детища.

    Это тоже ошибка. Во-первых, см. выше: DI — общеизвестный паттерн. Во-вторых, DI (в отличие от прямых зависимостей) как раз упрощает исследование архитектуры, потому что показывает все зависимости кода в его контракте.


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

    "Говорят", что DI как раз позволяет уменьшить стоимость таких доработок… нет?


    И нет, (правильно примененный) DI не нарушает принцип инкапсуляции.


    1. om2804
      10.04.2017 00:18

      когда вы используете Service Locator, вы не используете IoC, у вас «прямое» управление.

      Точно? На MSDN(раздел Related Patterns) написано, что Service Locator является специализированной версией IoC
      Я до конца не доверяю википедии(раздел Implementation techniques), но там тоже сказано об этом.
      Дак Service Locator является реализацией IoC или нет?


      1. lair
        10.04.2017 00:23
        +1

        С моей точки зрения — не является, потому что зависящий компонент (потребитель) сам запрашивает (=прямое управление) экземпляр компонента, от которого он зависит (сервиса). При инвертированном управлении он должен получать этот экземпляр извне.


        Service Locator и Dependency Injection оба являются методами реализации Dependency Inversion, которую часто приравнивают к Inversion of Control — видимо, на основании общего слова Inversion — но это разные паттерны.


        1. om2804
          10.04.2017 00:39

          Да, компонент сам запрашивает то, что ему нужно в коде. Но он сам не создаёт конкретных реализаций. Он запрашивает их у локатора по абстракции. Это ли ни IoC? И тоже самое при инъекции в конструктор компонент всё равно прописывает то, что ему нужно только в публичном «контракте». Сути это не поменяло, только зависимости стали прозрачными и явными.

          Dependency Inversion

          Это Вы про DIP?


          1. lair
            10.04.2017 00:42

            Да, компонент сам запрашивает, то что ему нужно в коде. Но он сам не создаёт конкретных реализаций. Он запрашивает их у локатора по абстракции. Это ли ни IoC?

            Неоднозначный вопрос. С моей точки зрения, нет, потому что последовательность вызовов (а IoC — это про control flow) — прямая.


            И тоже самое при инъекции в конструктор компонент всё равно прописывает то, что ему нужно только в публичном «контракте». Сути это не поменяло, только зависимости стали прозрачными и явными.

            Зависит от того, что вы считаете сутью. Для меня "суть" IoC — это "кто кого вызывает".


            Это вы про DIP?

            Да, я про него.


            1. om2804
              10.04.2017 01:06

              Ок. Я Вас понял. Спасибо за ответ.


        1. areht
          10.04.2017 10:55

          > С моей точки зрения — не является, потому что зависящий компонент (потребитель) сам запрашивает (=прямое управление) экземпляр компонента, от которого он зависит (сервиса).

          А если компонент получил в конструктор Lazy<T> — это прямое управление?


          1. lair
            10.04.2017 11:56

            "Более прямое". А Func<T> — еще более прямое. Это же не то что бы триггер, это континуум.


            1. areht
              10.04.2017 12:12

              IoC же не самоцель, он должен приносить конкретные профиты. Он их или приносит, или нет. ИМХО нет смысла говорить о «более прямом», если профиты остались те же.


              1. lair
                10.04.2017 12:15

                А я нигде не говорю, что IoC — самоцель, или что надо делать вот так или вот так. Я просто говорю, что конкретную реализацию/поведение — как мне кажется — лучше называть иначе.


      1. mayorovp
        10.04.2017 09:22

        Дак Service Locator является реализацией IoC или нет?

        Что такое сферическое IoC в вакууме, особенно в отрыве от DI — не знаю, но любой IoC-контейнер по своей природе является реализацией Service Locator. Другое дело, что использовать его таким образом нужно в очень ограниченном количестве места, желательно — в одном.


        1. VolCh
          10.04.2017 09:42

          но любой IoC-контейнер по своей природе является реализацией Service Locator

          Скорее не является, а содержит и использует.


        1. om2804
          10.04.2017 22:04

          Название IoC-контейнер лишено всякого смысла, если посмотреть что такое IoC. DI-контейнер как это названо у Симана мне нравится больше. Контейнер можно использовать как Service Locator, но вопрос был в другом


  1. lair
    03.04.2017 14:41
    +1

    С точки зрения теории разработки ПО лично мне гораздо чаще приходилось читать или слышать хвалебные статьи и отзывы об IoC/DI, но, как всегда, критика тоже есть. Можно ознакомиться, например [...] тут (Хабр)

    А вы ссылку "тут" прочитали, прежде, чем давать? Там как раз нет ни строчки критики DI, там есть набор антипаттернов, решением которых является DI.