Если я правильно понимаю, типичным механизмом внедрения зависимостей является внедрение либо через конструктор класса, либо через публичное свойство (член) класса.
Это раскрывает внедряемую зависимость и нарушает принцип инкапсуляции ООП.
Правильно ли я определил этот компромисс? Как вы справляетесь с этой проблемой?
См. Также мой ответ на мой вопрос ниже.
Ответы:
Есть еще один способ взглянуть на этот вопрос, который может вас заинтересовать.
Когда мы используем IoC / внедрение зависимостей, мы не используем концепции ООП. По общему признанию, мы используем объектно-ориентированный язык в качестве «хоста», но идеи, лежащие в основе IoC, исходят из компонентно-ориентированной разработки программного обеспечения, а не объектно-ориентированного программирования.
Компонентное программное обеспечение предназначено для управления зависимостями - широко используемым примером является механизм сборки .NET. Каждая сборка публикует список сборок, на которые она ссылается, и это значительно упрощает сборку (и проверку) частей, необходимых для работающего приложения.
Применяя аналогичные методы в наших объектно-ориентированных программах через IoC, мы стремимся упростить настройку и обслуживание программ. Публикация зависимостей (в виде параметров конструктора или чего-то еще) является ключевой частью этого. Инкапсуляция на самом деле неприменима, поскольку в мире, ориентированном на компоненты / услуги, не существует «типа реализации», из которого можно было бы просочиться.
К сожалению, наши языки в настоящее время не отделяют мелкозернистые, объектно-ориентированные концепции от более грубых, компонентно-ориентированных, так что это различие, о котором вы должны помнить только :)
источник
Это хороший вопрос, но в какой-то момент инкапсуляция в чистом виде должна быть нарушена, если для объекта когда-либо будет выполнена его зависимость. Некоторый поставщик зависимости должен знать, что рассматриваемый объект требует a
Foo
, и поставщик должен иметь способ предоставитьFoo
объекту.Классически этот последний случай обрабатывается, как вы говорите, с помощью аргументов конструктора или методов установки. Однако это не обязательно так - я знаю, что последние версии среды Spring DI на Java, например, позволяют аннотировать частные поля (например, с помощью
@Autowired
), и зависимость будет установлена с помощью отражения без необходимости раскрывать зависимость через любой из общедоступных методов / конструкторов классов. Возможно, это именно то решение, которое вы искали.Тем не менее, я не думаю, что внедрение конструктора является большой проблемой. Я всегда считал, что объекты должны быть полностью действительными после создания, так что все, что им нужно для выполнения своей роли (т.е. находиться в допустимом состоянии), в любом случае должно передаваться через конструктор. Если у вас есть объект, для работы которого требуется соавтор, мне кажется нормально, что конструктор публично объявляет это требование и обеспечивает его выполнение при создании нового экземпляра класса.
В идеале, имея дело с объектами, вы все равно взаимодействуете с ними через интерфейс, и чем больше вы это делаете (и у вас есть зависимости, подключенные через DI), тем меньше вам на самом деле приходится иметь дело с конструкторами самостоятельно. В идеальной ситуации ваш код не имеет отношения к конкретным экземплярам классов и даже не создает их; поэтому ему просто дается
IFoo
сквозной DI, не беспокоясь о том, что конструкторFooImpl
указывает, что ему нужно выполнять свою работу, и на самом деле даже не осознаваяFooImpl
существовании. С этой точки зрения инкапсуляция идеальна.Это, конечно, мнение, но, на мой взгляд, DI не обязательно нарушает инкапсуляцию, и на самом деле может помочь ей, централизовав все необходимые знания о внутреннем устройстве в одном месте. Это не только хорошо само по себе, но еще лучше, что это место находится за пределами вашей собственной кодовой базы, поэтому ни один из написанных вами кодов не должен знать о зависимостях классов.
источник
Ну, честно говоря, инкапсуляцию все нарушает. :) Это своего рода нежный принцип, к которому нужно относиться хорошо.
Итак, что нарушает инкапсуляцию?
Наследование делает .
Аспектно-ориентированное программирование делает . Например, вы регистрируете обратный вызов onMethodCall (), и это дает вам прекрасную возможность внедрить код в обычную оценку метода, добавив странные побочные эффекты и т. Д.
Объявление друга в C ++ делает .
Расширение класса в Ruby делает . Просто переопределите строковый метод где-нибудь после того, как строковый класс был полностью определен.
Ну, много чего делает .
Инкапсуляция - хороший и важный принцип. Но не единственный.
источник
Да, DI нарушает инкапсуляцию (также известную как «скрытие информации»).
Но настоящая проблема возникает, когда разработчики используют это как предлог для нарушения принципов KISS (Keep It Short and Simple) и YAGNI (You Ain't Gonna Need It).
Лично я предпочитаю простые и эффективные решения. Я в основном использую оператор «новый» для создания экземпляров зависимостей с отслеживанием состояния, когда и где они необходимы. Он прост, хорошо инкапсулирован, прост для понимания и тестирования. Так почему не?
источник
Хороший контейнер / система для внедрения зависимостей допускает внедрение конструктора. Зависимые объекты будут инкапсулированы, и их вообще не нужно будет открывать публично. Кроме того, при использовании системы DP никто из вашего кода даже не «знает» детали того, как создается объект, возможно, даже включая создаваемый объект. В этом случае больше инкапсуляции, поскольку почти весь ваш код не только защищен от знаний об инкапсулированных объектах, но даже не участвует в построении объектов.
Теперь я предполагаю, что вы сравниваете со случаем, когда созданный объект создает свои собственные инкапсулированные объекты, скорее всего, в своем конструкторе. В моем понимании DP мы хотим снять эту ответственность с объекта и передать ее кому-то другому. С этой целью «кто-то другой», который в данном случае является контейнером DP, действительно обладает глубокими знаниями, которые «нарушают» инкапсуляцию; Преимущество в том, что он извлекает эти знания из самого объекта. Кто-то должен это иметь. Остальная часть вашего приложения этого не делает.
Я бы подумал об этом так: контейнер / система внедрения зависимости нарушает инкапсуляцию, а ваш код - нет. Фактически, ваш код более "инкапсулирован", чем когда-либо.
источник
Как отметил Джефф Стернал в комментарии к вопросу, ответ полностью зависит от того, как вы определяете инкапсуляцию .
Кажется, есть два основных лагеря того, что означает инкапсуляция:
File
объект может иметь методы дляSave
,Print
,Display
,ModifyText
и т.д.Эти два определения прямо противоречат друг другу. Если
File
объект может печатать сам себя, это будет во многом зависеть от поведения принтера. С другой стороны, если он просто знает о чем-то, что может печатать для него (оIFilePrinter
каком-либо таком интерфейсе), тоFile
объекту не нужно ничего знать о печати, и поэтому работа с ним принесет меньше зависимостей в объект.Итак, внедрение зависимостей нарушит инкапсуляцию, если вы воспользуетесь первым определением. Но, честно говоря, я не знаю, нравится ли мне первое определение - оно явно не масштабируется (если бы это было так, MS Word был бы одним большим классом).
С другой стороны, внедрение зависимостей почти обязательно, если вы используете второе определение инкапсуляции.
источник
Это не нарушает инкапсуляцию. Вы предоставляете соавтора, но класс решает, как его использовать. Пока вы следите за Телль, не спрашивайте, все в порядке. Я считаю, что инъекция конструктора предпочтительнее, но сеттеры тоже могут подойти, если они умны. То есть они содержат логику для поддержания инвариантов, которые представляет класс.
источник
Это похоже на ответ, за который проголосовали, но я хочу подумать вслух - возможно, другие тоже так видят вещи.
Классический объектно-ориентированный объект использует конструкторы для определения открытого контракта «инициализации» для потребителей класса (скрытие ВСЕХ деталей реализации; инкапсуляция). Этот контракт может гарантировать, что после создания у вас будет готовый к использованию объект (т. Е. Никаких дополнительных шагов инициализации, которые нужно запомнить (т. Е. Забыть) пользователем).
(конструктор) DI, несомненно, нарушает инкапсуляцию, пропуская детали реализации через этот общедоступный интерфейс конструктора. Пока мы по-прежнему считаем открытый конструктор ответственным за определение контракта инициализации для пользователей, мы создали ужасное нарушение инкапсуляции.
Теоретический пример:
Класс Foo имеет 4 метода и требует целого числа для инициализации, поэтому его конструктор выглядит как Foo (размер int), и пользователям класса Foo сразу ясно, что они должны предоставить размер. при экземпляра, чтобы Foo работал.
Скажем, этой конкретной реализации Foo может также потребоваться IWidget для выполнения своей работы. Внедрение этой зависимости в конструктор заставило бы нас создать конструктор типа Foo (размер int, виджет IWidget)
Что меня раздражает по этому поводу, так это то, что теперь у нас есть конструктор, который смешивает данные инициализации с зависимостями - один ввод представляет интерес для пользователя класса ( размера ), другой - внутренняя зависимость, которая только сбивает пользователя с толку и является реализацией. деталь ( виджет ).
Параметр размера НЕ является зависимостью - это просто значение инициализации для каждого экземпляра. IoC отлично подходит для внешних зависимостей (например, виджета), но не для инициализации внутреннего состояния.
Хуже того, что, если виджет необходим только для 2 из 4 методов этого класса; Я могу понести накладные расходы на создание экземпляра для Widget, даже если он не может использоваться!
Как это скомпрометировать / примирить?
Один из подходов - переключиться исключительно на интерфейсы для определения контракта операции; и отменить использование конструкторов пользователями. Для обеспечения согласованности все объекты должны быть доступны только через интерфейсы, а экземпляры должны создаваться только через некоторую форму преобразователя (например, контейнер IOC / DI). Только контейнер может создавать экземпляры вещей.
Это позаботится о зависимости виджетов, но как мы инициализируем «размер», не прибегая к отдельному методу инициализации в интерфейсе Foo? Используя это решение, мы потеряли возможность гарантировать, что экземпляр Foo полностью инициализирован к тому времени, когда вы его получите. Облом, потому что мне очень нравится идея и простота внедрения конструктора.
Как мне добиться гарантированной инициализации в этом мире DI, когда инициализация БОЛЬШЕ, чем ТОЛЬКО внешние зависимости?
источник
Чистая инкапсуляция - это идеал, которого никогда не достичь. Если бы все зависимости были скрыты, DI вообще не понадобился бы. Подумайте об этом так: если у вас действительно есть частные значения, которые могут быть интернализованы внутри объекта, скажем, например, целочисленное значение скорости объекта автомобиля, тогда у вас нет внешней зависимости и нет необходимости инвертировать или вводить эту зависимость. Такие значения внутреннего состояния, которые используются исключительно частными функциями, - это то, что вы хотите всегда инкапсулировать.
Но если вы строите автомобиль, которому нужен объект двигателя определенного типа, тогда у вас есть внешняя зависимость. Вы можете либо создать экземпляр этого движка - например, new GMOverHeadCamEngine () - внутри конструктора объекта автомобиля, сохраняя инкапсуляцию, но создавая гораздо более коварную связь с конкретным классом GMOverHeadCamEngine, либо вы можете внедрить его, позволяя вашему объекту Car работать агностически (и гораздо более надежно), например, на интерфейсе IEngine без конкретной зависимости. Независимо от того, используете ли вы контейнер IOC или простой DI для достижения этого, не имеет значения - дело в том, что у вас есть Автомобиль, который может использовать многие виды движков без привязки к любому из них, что делает вашу кодовую базу более гибкой и менее подвержен побочным эффектам.
DI не является нарушением инкапсуляции, это способ минимизировать взаимосвязь, когда инкапсуляция обязательно нарушается, как само собой разумеющееся практически в каждом проекте ООП. Внедрение зависимости в интерфейс извне минимизирует побочные эффекты связывания и позволяет вашим классам не зависеть от реализации.
источник
Это зависит от того, действительно ли зависимость является деталью реализации или чем-то, о чем клиент хотел бы / должен был знать так или иначе. Важно одно: на какой уровень абстракции нацелен класс. Вот некоторые примеры:
Если у вас есть метод, который использует кеширование под капотом для ускорения вызовов, тогда объект кеша должен быть синглтоном или чем-то еще и не должен вводиться. Тот факт, что кеш вообще используется, является деталью реализации, о которой клиенты вашего класса не должны заботиться.
Если вашему классу необходимо выводить потоки данных, вероятно, имеет смысл внедрить выходной поток, чтобы класс мог легко выводить результаты в массив, файл или куда-нибудь еще, кто может захотеть отправить данные.
Предположим, что у вас есть класс, который выполняет симуляцию Монте-Карло. Ему нужен источник случайности. С одной стороны, тот факт, что ему это нужно, является деталью реализации, поскольку клиенту действительно все равно, откуда берется случайность. С другой стороны, поскольку реальные генераторы случайных чисел делают компромиссы между степенью случайности, скоростью и т. Д., Которые клиент может захотеть контролировать, а клиент может захотеть контролировать раздачу, чтобы получить повторяемое поведение, внедрение может иметь смысл. В этом случае я бы предложил предложить способ создания класса без указания генератора случайных чисел и использовать локальный синглтон потока по умолчанию. Если / когда возникает необходимость в более точном управлении, предоставьте другой конструктор, который позволяет вводить источник случайности.
источник
Я верю в простоту. Применение IOC / внедрения зависимостей в доменных классах не дает никаких улучшений, за исключением того, что код становится более сложным для работы с кодом из-за наличия внешних xml-файлов, описывающих отношение. Многие технологии, такие как EJB 1.0 / 2.0 и struts 1.1, обращаются вспять, уменьшая количество материала, помещаемого в XML, и пытаются поместить их в код в качестве аннотации и т. Д. Таким образом, применение IOC для всех разрабатываемых вами классов сделает код бессмысленным.
IOC имеет свои преимущества, когда зависимый объект не готов к созданию во время компиляции. Это может происходить в большинстве компонентов архитектуры абстрактного уровня инфраструктуры, пытаясь установить общую базовую структуру, которая может потребоваться для различных сценариев. В этих местах использование IOC имеет больше смысла. Тем не менее, это не делает код более простым / поддерживаемым.
Как и у всех других технологий, у этой тоже есть свои плюсы и минусы. Меня беспокоит, что мы внедряем новейшие технологии повсюду, независимо от их наилучшего использования в контексте.
источник
Инкапсуляция нарушается только в том случае, если класс несет ответственность за создание объекта (что требует знания деталей реализации) и затем использует класс (который не требует знания этих деталей). Я объясню почему, но сначала краткая автомобильная анаология:
Но вернемся к кодировке. Инкапсуляция - это «сокрытие деталей реализации от чего-либо, использующего эту реализацию». Инкапсуляция - это хорошо, потому что детали реализации могут измениться без ведома пользователя класса.
При использовании внедрения зависимостей внедрение конструктора используется для создания службы. объектов типа (в отличие от объектов сущностей / значений, которые имеют состояние модели). Любые переменные-члены в объекте типа службы представляют детали реализации, которые не должны просачиваться. например, номер порта сокета, учетные данные базы данных, другой класс, вызываемый для шифрования, кэш и т. д.
Конструктор имеет значение , когда класс сначала было создано. Это происходит на этапе построения, когда ваш контейнер DI (или фабрика) связывает воедино все служебные объекты. Контейнер DI знает только детали реализации. Он знает все о деталях реализации, как ребята на заводе Kombi знают о свечах зажигания.
Во время выполнения созданный объект службы называется apon и выполняет реальную работу. В это время вызывающий объект ничего не знает о деталях реализации.
Теперь вернемся к инкапсуляции. Если детали реализации изменяются, то класс, использующий эту реализацию во время выполнения, изменять не нужно. Инкапсуляция не нарушена.
Если детали реализации меняются, контейнер DI (или фабрику) нужно изменить. Вы никогда не пытались скрыть детали реализации от фабрики.
источник
Поработав над этой проблемой немного дальше, я теперь считаю, что внедрение зависимостей (в настоящее время) в некоторой степени нарушает инкапсуляцию. Не поймите меня неправильно - я думаю, что в большинстве случаев использование внедрения зависимостей стоит того.
Причина, по которой DI нарушает инкапсуляцию, становится ясна, когда компонент, над которым вы работаете, должен быть доставлен «внешней» стороне (подумайте о написании библиотеки для клиента).
Когда мой компонент требует, чтобы субкомпоненты были введены через конструктор (или общедоступные свойства), нет никакой гарантии для
В то же время нельзя сказать, что
Обе цитаты взяты из Википедии .
Приведу конкретный пример: мне нужно предоставить клиентскую DLL, которая упрощает и скрывает связь со службой WCF (по сути, это удаленный фасад). Поскольку это зависит от 3 разных классов прокси WCF, если я использую подход DI, я вынужден открывать их через конструктор. Этим я раскрываю внутренности своего коммуникационного уровня, которые пытаюсь скрыть.
Вообще я полностью за Д.И. В этом конкретном (крайнем) примере это кажется мне опасным.
источник
DI нарушает инкапсуляцию для NON-Shared объектов - точка. Общие объекты имеют срок жизни за пределами создаваемого объекта, и поэтому должны быть АГРЕГАТИВНЫ в создаваемый объект. Объекты, которые являются частными для создаваемого объекта, должны быть СОСТАВЛЕНЫ в созданный объект - когда созданный объект уничтожается, он принимает составной объект с собой. Возьмем, к примеру, человеческое тело. Что составлено и что агрегировано. Если бы мы использовали DI, конструктор человеческого тела имел бы сотни объектов. Например, многие органы (потенциально) можно заменить. Но они по-прежнему входят в состав тела. Клетки крови создаются (и разрушаются) в организме каждый день, без необходимости во внешних воздействиях (кроме белков). Таким образом, клетки крови создаются внутри организма - new BloodCell ().
Сторонники DI утверждают, что объект НИКОГДА не должен использовать оператор new. Такой «пуристский» подход нарушает не только инкапсуляцию, но и принцип замещения Лискова для всех, кто создает объект.
источник
Я тоже боролся с этим представлением. Сначала «требование» использовать контейнер DI (например, Spring) для создания экземпляра объекта ощущалось как прыжок через обручи. Но на самом деле это не обруч - это просто еще один «опубликованный» способ создания нужных мне объектов. Конечно, инкапсуляция «нарушена», потому что кто-то «вне класса» знает, что ему нужно, но на самом деле это знает не остальная часть системы, а контейнер DI. Ничего волшебного не происходит иначе, потому что ДИ «знает», что один объект нуждается в другом.
На самом деле это становится еще лучше - сосредоточив внимание на фабриках и репозиториях, мне даже не нужно знать, что DI вообще участвует! Для меня это ставит крышку обратно на герметизацию. Уф!
источник
PS. Предоставляя внедрение зависимостей, вы не обязательно нарушаете инкапсуляцию . Пример:
Клиентский код до сих пор не знает деталей реализации.
источник
Возможно, это наивный способ думать об этом, но в чем разница между конструктором, который принимает целочисленный параметр, и конструктором, который принимает службу в качестве параметра? Означает ли это, что определение целого числа вне нового объекта и передача его в объект нарушает инкапсуляцию? Если служба используется только в новом объекте, я не понимаю, как это может нарушить инкапсуляцию.
Кроме того, за счет использования какой-то функции автоматического подключения (например, Autofac для C #) код становится очень чистым. Создав методы расширения для построителя Autofac, я смог вырезать МНОГО кода конфигурации DI, который мне пришлось бы поддерживать с течением времени по мере роста списка зависимостей.
источник
Я думаю, что самоочевидно, что как минимум DI значительно ослабляет инкапсуляцию. В дополнение к этому, следует рассмотреть еще несколько недостатков DI.
Это затрудняет повторное использование кода. Модуль, который клиент может использовать без явного указания зависимостей, очевидно, проще в использовании, чем модуль, в котором клиент должен каким-то образом обнаружить зависимости этого компонента, а затем каким-то образом сделать их доступными. Например, компонент, изначально созданный для использования в приложении ASP, может ожидать, что его зависимости будут предоставляться контейнером DI, который предоставляет экземплярам объектов время жизни, связанное с клиентскими http-запросами. Это может быть непросто воспроизвести на другом клиенте, который не имеет того же встроенного контейнера DI, что и исходное приложение ASP.
Это может сделать код более хрупким. Зависимости, предоставляемые спецификацией интерфейса, могут быть реализованы неожиданными способами, что приводит к возникновению целого класса ошибок времени выполнения, которые невозможны при статически разрешенной конкретной зависимости.
Это может сделать код менее гибким в том смысле, что у вас будет меньше вариантов выбора того, как вы хотите, чтобы он работал. Не каждому классу необходимо, чтобы все его зависимости существовали на протяжении всего времени существования экземпляра-владельца, но со многими реализациями DI у вас нет другого выбора.
Имея это в виду, я думаю, что наиболее важным вопросом становится следующий: « Нужно ли вообще определять конкретную зависимость извне? ». На практике мне редко приходилось создавать зависимость, поставляемую извне, только для поддержки тестирования.
Если зависимость действительно должна поставляться извне, это обычно предполагает, что отношение между объектами является взаимодействием, а не внутренней зависимостью, и в этом случае соответствующей целью является инкапсуляция каждого класса, а не инкапсуляция одного класса внутри другого. ,
По моему опыту, основная проблема, связанная с использованием DI, заключается в том, что независимо от того, начинаете ли вы со среды приложения со встроенным DI или добавляете поддержку DI в свою кодовую базу, по некоторым причинам люди предполагают, что, поскольку у вас есть поддержка DI, это должно быть правильное способ создать все . Они просто никогда не удосужились задать вопрос «нужно ли указывать эту зависимость извне?». И что еще хуже, они также начинают пытаться заставить всех остальных использовать поддержку DI для всего. .
Результатом этого является то, что ваша кодовая база неумолимо начинает переходить в состояние, когда для создания любого экземпляра чего-либо в вашей кодовой базе требуется множество тупых конфигураций контейнера DI, а отладка чего-либо вдвое сложнее, потому что у вас есть дополнительная рабочая нагрузка, пытаясь определить, как и где что-либо было создано.
Итак, мой ответ на вопрос таков. Используйте DI, где вы можете определить реальную проблему, которую он решает за вас, которую вы не можете решить более простым способом.
источник
Я согласен с тем, что в крайнем случае DI может нарушать инкапсуляцию. Обычно DI предоставляет зависимости, которые никогда по-настоящему не инкапсулировались. Вот упрощенный пример из книги Мишко Хевери « Синглтоны - патологические лжецы» :
Вы начинаете с теста CreditCard и пишете простой модульный тест.
В следующем месяце вы получите счет на 100 долларов. Почему с вас сняли обвинение? Модульный тест затронул производственную базу данных. Внутренние звонки по кредитной карте
Database.getInstance()
. Рефакторинг CreditCard таким образом, чтобы он принималDatabaseInterface
в своем конструкторе, раскрывает факт наличия зависимости. Но я бы сказал, что зависимость никогда не была инкапсулирована с самого начала, поскольку класс CreditCard вызывает внешне видимые побочные эффекты. Если вы хотите протестировать CreditCard без рефакторинга, вы, безусловно, можете наблюдать зависимость.Я не думаю, что стоит беспокоиться о том, уменьшает ли инкапсуляцию представление базы данных как зависимости, потому что это хороший дизайн. Не все решения DI будут такими простыми. Однако ни один из других ответов не показывает встречного примера.
источник
CreditCard
объект может измениться с WebAPI на PayPal без изменения чего-либо внешнего по отношению к классу?Думаю, дело в размахе. Когда вы определяете инкапсуляцию (не давая знать как), вы должны определить, что такое инкапсулированная функциональность.
Класс как есть : то, что вы инкапсулируете, является единственной обязанностью класса. Что умеет делать. Например, сортировка. Если вы вводите какой-то компаратор для заказа, скажем, клиентов, это не является частью инкапсулированной вещи: быстрой сортировки.
Настроенная функциональность : если вы хотите предоставить готовую к использованию функциональность, вы предоставляете не класс QuickSort, а экземпляр класса QuickSort, настроенный с помощью Comparator. В этом случае код, отвечающий за создание и настройку, должен быть скрыт от кода пользователя. И это инкапсуляция.
Когда вы программируете классы, то есть реализуя отдельные обязанности в классы, вы используете вариант 1.
Когда вы программируете приложения, когда вы делаете что-то, что выполняет некоторую полезную конкретную работу, вы постоянно используете вариант 2.
Это реализация настроенного экземпляра:
Вот как его использует другой клиентский код:
Он инкапсулирован, потому что если вы измените реализацию (вы измените
clientSorter
определение bean-компонента), это не нарушит работу клиента. Может быть, когда вы используете XML-файлы, все написанные вместе, вы видите все детали. Но поверьте, клиентский код (ClientService
) ничего не знает о своем сортировщике.источник
Вероятно, стоит упомянуть, что
Encapsulation
это несколько зависит от перспективы.С точки зрения того, кто работает над
A
классом, во втором примереA
гораздо меньше известно о природеthis.b
А без DI
против
Человек, просматривающий этот код, знает больше о природе
A
во втором примере.С DI по крайней мере все просочившиеся знания находятся в одном месте.
источник