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

20

Есть много причин, почему глобалы злы в ООП.

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

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

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

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

Наивным решением было бы просто создать из них глобальный контейнер, например

vector<Vehicle> vehicles;

к которому можно получить доступ откуда угодно.

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

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

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

ВСЗ
источник
6
Подождите, вы говорите о не изменяемых глобалах и используете ссылку "почему глобальное состояние плохо", чтобы оправдать это? WTF?
Теластин
1
Я не оправдываю глобалы вообще. Я думаю, ясно, что я согласен с тем фактом, что глобальные решения не являются хорошим решением. Я просто говорю о ситуации, когда даже использование внедрения зависимостей может быть не намного лучше, чем (или, может быть, даже почти неотличимо от) глобальных переменных. Таким образом, ответ может указать на другие скрытые преимущества от использования инъекции зависимостей в этой ситуации, или на то, что другие подходы будут лучше, чем di
vsz
9
Но есть определенная разница между глобальными значениями только для чтения и глобальным состоянием.
Теластин
1
@vsz не совсем - вопрос о фабриках поиска сервисов как обойти проблему множества различных объектов; но его ответы также отвечают на ваш вопрос о предоставлении классам доступа к глобальным данным, а не к глобальным данным, передаваемым классам.
gbjbaanb

Ответы:

37

в случае, когда почти каждый должен знать об определенной структуре данных, почему Dependency Injection лучше, чем глобальный объект?

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

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

Точка внедрения зависимости:

  1. Предоставить субъектам доступ к ресурсам по мере необходимости , и
  2. Чтобы контролировать, к какому экземпляру ресурса обращается любой данный субъект.

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

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

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

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

Затем вы должны подумать: действительно ли всем актерам нужен доступ к этому ресурсу? действительно?

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

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

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

  • Рефакторинг ваших актеров, чтобы они в свою очередь принимали в качестве параметров только те подмножества проблемы, над которыми им нужно работать, чтобы им не приходилось постоянно консультироваться с каким-то большим огромным центральным ресурсом.

Майк Накис
источник
Я не уверен, что понимаю это - с одной стороны, вы говорите, что DI позволяет вам использовать несколько экземпляров ресурса, а глобальные - нет, что явно бессмысленно, если вы не объединяете одну статическую переменную с объектами с множественными экземплярами - нет причин что «глобальный» должен быть либо отдельным экземпляром, либо одним классом.
gbjbaanb
3
@gbjbaanb Предположим, что ресурс Zork. ОП говорит, что не только каждому объекту в его системе необходим Zork для работы, но также и то, что когда-либо будет существовать только один Zork, и что все его объекты будут нуждаться только в доступе к этому одному экземпляру Zork. Я говорю, что ни одно из этих двух предположений не является разумным: вероятно, это неправда, что все его объекты нуждаются в Zork, и будут моменты, когда некоторым объектам потребуется определенный экземпляр Zork, в то время как другим объектам потребуется другой экземпляр Zork.
Майк Накис
2
@ gbjbaanb Я не думаю, что вы просите меня объяснить, почему было бы глупо иметь два глобальных экземпляра Zork, называть их ZorkA и ZorkB и жестко кодировать объекты, один из которых будет использовать ZorkA, а какой - ZorkB, право?
Майк Накис
1
Я не сказал всем, я сказал почти всем. Суть вопроса заключалась в том, имеет ли DI другие преимущества, кроме отказа в доступе тем немногим актерам, которым это не нужно. Я знаю, что "объекты бога" - это анти-паттерн, но слепое следование лучшим практикам также может быть одним из них. Может случиться так, что вся цель программы - делать разные вещи с определенным ресурсом, в этом случае почти каждый нуждается в доступе к этому ресурсу. Хорошим примером может служить программа, которая работает с изображением: почти все в нем имеет отношение к данным изображения.
Вс
1
@gbjbaanb Я считаю, что идея состоит в том, чтобы один клиентский класс мог работать исключительно на ZorkA или ZorkB, как указано, а не на том, чтобы клиентский класс решал, какой из них захватить.
Евгений Рябцев
39

Существует множество причин, по которым неизменяемые глобалы являются злом в ООП.

Это сомнительное утверждение. Ссылка используется в качестве доказательства относится к государственному - изменяемым глобал. Они явно злые. Только для чтения глобалы являются только константами. Константы относительно вменяемые. Я имею в виду, вы не собираетесь вводить значение числа Пи во все ваши классы, не так ли?

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

Нет, они не

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

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

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

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

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

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

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

Telastyn
источник
извините за путаницу с "не изменяемым", я хотел сказать изменяемый, и почему-то не признал мою ошибку.
вс
«Это дизайнерский кошмар» - я знаю, что это так. Но если требования состоят в том, что все эти события должны иметь возможность вносить изменения в данные, тогда события будут связаны с данными, даже если я разделю и скрою их под слоем абстракций. Вся программа об этих данных. Например, если программе необходимо выполнить много различных операций с изображением, тогда все или почти все ее классы будут каким-то образом связаны с данными изображения. Просто сказать «давайте напишем программу, которая делает что-то другое», не приемлемо.
вс
2
Приложения, которые имеют много объектов, которые регулярно обращаются к какой-либо базе данных, не являются беспрецедентными.
Роберт Харви,
@RobertHarvey - конечно, но интерфейс, от которого они зависят, «может получить доступ к базе данных» или «какое-то постоянное хранилище данных для foos»?
Теластин,
1
Напоминает мне о Дилберте, где PHB просит 500 функций, а Дилберт говорит «нет». Так что PHB просит 1 функцию, а Дилберт говорит, что уверен. На следующий день Дилберт получает 500 запросов на каждую функцию. Если что-то не масштабируется, оно просто не масштабируется, независимо от того, как вы его одеваете.
CorsiKa
16

Есть три основные причины, которые вы должны рассмотреть.

  1. Читаемость. Если каждая единица кода имеет все, что ей нужно для работы с введенным или переданным в качестве параметра, легко взглянуть на код и сразу увидеть, что он делает. Это дает вам функциональность, которая также позволяет вам лучше разделять проблемы, а также заставляет задуматься о ...
  2. Модульность. Зачем парсеру знать весь список транспортных средств и все их свойства? Вероятно, нет. Возможно, он должен запросить, существует ли идентификатор транспортного средства или имеет ли транспортное средство X свойство Y. Отлично, это означает, что вы можете написать сервис, который делает это, и внедрить только этот сервис в свой анализатор. Внезапно вы получаете дизайн, который имеет гораздо больше смысла, так как каждый бит кода обрабатывает только те данные, которые имеют к нему отношение. Что приводит нас к ...
  3. Тестируемость. Для типового модульного теста вы хотите настроить свою среду для множества различных сценариев, и внедрение делает это очень простым в реализации. Опять же, возвращаясь к примеру с парсером, вы действительно хотите всегда создавать вручную полный список полноценных транспортных средств для каждого тестового примера, который вы пишете для своего парсера? Или вы просто создадите фиктивную реализацию вышеупомянутого сервисного объекта, возвращая различное количество идентификаторов? Я знаю, какой бы я выбрал.

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

biziclop
источник
+1 за краткий ответ, сосредоточенный на вопросах обслуживания. Больше ответов P: SE должно быть таким.
dodgethesteamroller
1
Ваша сумма так хороша. Тааак хорошо. Если вы думаете, что [шаблон дизайна месяца] или [модное слово месяца] волшебным образом решит ваши проблемы, у вас будет плохое время.
CorsiKa,
@corsiKa Я был против DI (или Spring, если быть более точным) долгое время, потому что я не мог понять смысл этого. Это просто выглядело очень хлопотно без ощутимой выгоды (это было еще в дни конфигурации XML). Хотел бы я найти документацию о том, что DI на самом деле покупает вас тогда. Но я этого не сделал, поэтому мне пришлось самому в этом разобраться. Это заняло много времени. :)
Бизиклоп
3

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

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

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

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

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

gbjbaanb
источник
«как правило, глобальный объект очень легко обслуживаем» - нет, если он изменчив. По моему опыту, наличие такого изменчивого глобального состояния может быть кошмаром (хотя есть способы сделать его терпимым).
Слёске
3

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

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

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

СЕЙЧАС, если у вашего объекта слишком много свойств - вы должны разбить ваш объект на дочерние объекты.

редактировать

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

Вам необходимо выполнить модульное тестирование кода GUI формы Windows. Если вы используете global, вам нужно будет создать все кнопки и их события, например, чтобы создать подходящий тестовый пример ...

Математика
источник
Верно, но, например, парсер должен знать все, потому что может быть получена любая команда, которая может вносить изменения во что угодно.
vsz
1
«Прежде чем я подойду к твоему актуальному вопросу» ... ты собираешься ответить на его вопрос?
gbjbaanb
@gbjbaanb вопрос содержит много текста, пожалуйста, дайте мне немного времени, чтобы прочитать его, правильно
Математика