Я с трудом ищу ресурсы о том, почему я должен использовать внедрение зависимости . Большинство ресурсов, которые я вижу, объясняют, что он просто передает экземпляр объекта другому экземпляру объекта, но почему? Это только для более чистой архитектуры / кода или это влияет на производительность в целом?
Почему я должен делать следующее?
class Profile {
public function deactivateProfile(Setting $setting)
{
$setting->isActive = false;
}
}
Вместо следующего?
class Profile {
public function deactivateProfile()
{
$setting = new Setting();
$setting->isActive = false;
}
}
deactivateProfile
мне кажется, что установка вisActive
false, не заботясь о своем предыдущем состоянии, является правильным подходом. Вызов метода по своей сути означает, что вы хотите установить его как неактивный, а не получить его текущий (неактивный) активный статус.Ответы:
Преимущество в том, что без внедрения зависимостей ваш класс Profile
Но с внедрением зависимости
Это может показаться (или даже быть) неуместным в данном конкретном случае, но представьте, если мы говорим не об объекте «Настройки», а об объекте «DataStore», который может иметь разные реализации, один для хранения данных в файлах, а другой для хранения их в база данных. А для автоматизированных тестов вам нужна и фиктивная реализация. Теперь вы действительно не хотите, чтобы класс Profile жестко кодировал тот, который он использует, и, что еще более важно, вы действительно не хотите, чтобы класс Profile знал о путях файловой системы, соединениях с БД и паролях, поэтому создание объектов DataStore имеет произойти где - то в другом месте.
источник
This may seem (or even be) irrelevant in this particular case
Я думаю, что это очень важно, на самом деле. Как бы вы получили настройки? Многие системы, которые я видел, будут иметь жестко заданный набор настроек по умолчанию и общедоступную конфигурацию, поэтому вам нужно будет загрузить обе и перезаписать некоторые значения общедоступными настройками. Вам может даже понадобиться несколько источников значений по умолчанию. Возможно, вы даже получаете некоторые с диска, другие с БД. Таким образом, вся логика даже для получения настроек может и часто является нетривиальной - определенно не то, что потребляет код, должно заботиться или не беспокоиться.$setting = new Setting();
ужасно неэффективным. Инъекция и создание объекта происходит один раз.Внедрение зависимостей облегчает тестирование вашего кода.
Я узнал об этом на собственном опыте, когда мне было поручено исправить трудно обнаруживаемую ошибку в интеграции Magento с PayPal.
Когда PayPal сообщает Magento о сбое платежа, возникает проблема: Magento не регистрирует сбой должным образом.
Тестирование потенциального исправления «вручную» было бы очень утомительным: вам нужно было бы каким-то образом вызвать «Неудачное» уведомление PayPal. Вы должны будете отправить электронную проверку, отменить ее и подождать, пока она не выдаст ошибку. Это означает, что для проверки односимвольного изменения кода требуется более 3 дней!
К счастью, кажется, что разработчики ядра Magento, которые разработали эту функцию, имели в виду тестирование и использовали шаблон внедрения зависимостей, чтобы сделать его тривиальным. Это позволяет нам проверить нашу работу с помощью простого контрольного примера, подобного следующему:
Я уверен, что у модели DI есть много других преимуществ, но увеличенная тестируемость - единственное большое преимущество в моем уме .
Если вам интересно решение этой проблемы, посмотрите репозиторий GitHub здесь: https://github.com/bubbleupdev/BUCorefix_Paypalstatus
источник
Почему (что даже проблема)?
Лучшая мнемоника, которую я нашел для этого, - « новое - это клей »: каждый раз, когда вы используете
new
в своем коде, этот код привязан к конкретной реализации . Если вы неоднократно используете new в конструкторах, вы создадите цепочку конкретных реализаций . И поскольку вы не можете «иметь» экземпляр класса, не создав его, вы не можете отделить эту цепочку.В качестве примера представьте, что вы пишете видеоигру для гоночных автомобилей. Вы начали с класса
Game
, который создаетRaceTrack
, который создает 8Cars
, каждый из которых создаетMotor
. Теперь , если вы хотите 4 дополнительныхCars
с другим ускорением, вам придется изменить каждый класс упоминается , за исключением , может бытьGame
.Более чистый код
Да .
Тем не менее, это может показаться менее ясным в этой ситуации, потому что это скорее пример того, как это сделать . Фактическое преимущество проявляется только в том случае, если задействованы несколько классов, и его труднее продемонстрировать, но представьте, что вы использовали DI в предыдущем примере. Код, создающий все эти вещи, может выглядеть примерно так:
Добавление этих 4 различных автомобилей теперь можно сделать, добавив следующие строки:
RaceTrack
,Game
,Car
, илиMotor
были необходимы - это означает , что мы можем быть 100% уверены , что мы не вводить никаких новых ошибок там!Вопросы производительности
Нет . Но, если быть полностью честным с тобой, это возможно.
Однако, даже в этом случае, это настолько смехотворно небольшое количество, что вам не нужно заботиться. Если в какой - то момент в будущем, вы должны написать код для тамагочи с эквивалентом 5MHZ процессора и оперативной памяти 2 Мб, то , возможно , вы , возможно , придется заботиться об этом.
В 99,999% * случаев производительность будет выше, потому что вы потратили меньше времени на исправление ошибок и больше времени на улучшение ресурсоемких алгоритмов.
* полностью составленный номер
Добавлена информация: "жестко закодировано"
Не ошибитесь, это является еще очень «жестко» - номера записываются непосредственно в коде. Не жестко закодированный означает что-то вроде сохранения этих значений в текстовом файле - например, в формате JSON - и последующего чтения их из этого файла.
Чтобы сделать это, вы должны добавить код для чтения файла и последующего анализа JSON. Если вы рассмотрите пример еще раз; в не-DI версии a
Car
или aMotor
теперь должны прочитать файл. Это не звучит так, как будто в этом слишком много смысла.В версии DI вы бы добавили его в код, настраивающий игру.
источник
Я всегда был озадачен введением зависимости. Казалось, что он существует только в сферах Java, но эти сферы говорили об этом с большим почтением. Видите ли, это был один из великих образцов, которые, как говорят, наводят порядок в хаосе. Но примеры всегда были замысловатыми и искусственными, создавая проблему, а затем решая ее путем усложнения кода.
Это имело больше смысла, когда один из разработчиков Python поделился со мной этой мудростью: она просто передает аргументы функциям. Это всего лишь образец; больше похоже на напоминание о том, что вы можете попросить что-то в качестве аргумента, даже если бы вы могли представить разумную ценность сами.
Итак, ваш вопрос примерно эквивалентен "почему моя функция должна принимать аргументы?" и имеет много одинаковых ответов, а именно: позволить вызывающему абоненту принимать решения .
Конечно, это обходится дорого, потому что теперь вы заставляете звонящего принимать какое-то решение (если вы не сделаете аргумент необязательным), а интерфейс несколько сложнее. В обмен вы получаете гибкость.
Итак: Есть ли веская причина, по которой вам конкретно нужно использовать именно этот
Setting
тип / значение? Есть ли веская причина, по которой вызывающий код может захотеть другойSetting
тип / значение? (Помните, тесты - это код !)источник
Пример, который вы приводите, не является инъекцией зависимостей в классическом смысле. Внедрение зависимостей обычно относится к передаче объектов в конструкторе или с помощью «установки сеттера» сразу после создания объекта, чтобы установить значение для поля во вновь созданном объекте.
Ваш пример передает объект в качестве аргумента методу экземпляра. Этот метод экземпляра затем изменяет поле в этом объекте. Внедрение зависимости? Разрушать инкапсуляцию и скрывать данные? Абсолютно!
Теперь, если код был такой:
Тогда я бы сказал, что вы используете внедрение зависимостей. Заметным отличием является то, что
Settings
объект передается в конструктор илиProfile
объект.Это полезно, если объект Settings является дорогим или сложным в построении, или если Settings - это интерфейс или абстрактный класс, где существует несколько конкретных реализаций для изменения поведения во время выполнения.
Поскольку вы непосредственно обращаетесь к полю объекта «Настройки», а не вызываете метод, вы не можете воспользоваться преимуществом Полиморфизма, который является одним из преимуществ внедрения зависимостей.
Похоже, что настройки для профиля являются специфическими для этого профиля. В этом случае я бы сделал одно из следующих действий:
Создание экземпляра объекта настроек внутри конструктора профиля
Передайте объект Settings в конструкторе и скопируйте отдельные поля, которые относятся к профилю.
Честно говоря, если передать объект «Настройки» в
deactivateProfile
и затем изменить внутреннее поле объекта «Настройки», это запах кода. Объект Settings должен быть единственным, изменяющим его внутренние поля.источник
The example you give is not dependency injection in the classical sense.
- Это не имеет значения. У ОО людей есть объекты в мозгу, но вы все равно передаете зависимость чему-то.Я знаю, что опаздываю на эту вечеринку, но чувствую, что упущен важный момент.
Ты не должен. Но не потому, что Dependency Injection - плохая идея. Это потому, что это делает это неправильно.
Давайте посмотрим на это с помощью кода. Мы собираемся сделать это:
когда мы получаем примерно то же самое из этого:
Так что, конечно, это кажется пустой тратой времени. Это когда вы делаете это таким образом. Это не лучшее использование инъекций зависимости. Это даже не лучшее использование класса.
Теперь, что если бы вместо этого у нас было это:
И теперь
application
он может активировать и деактивироватьprofile
без необходимости знать что-либо конкретное о том,setting
что он на самом деле меняется. Почему это хорошо? В случае, если вам нужно изменить настройки. Ониapplication
защищены от этих изменений, поэтому вы можете сойти с ума в безопасном замкнутом пространстве, не наблюдая, как все ломается, как только вы что-то трогаете.Это следует отдельной конструкции из принципа поведения . Шаблон DI здесь прост. Создайте все, что вам нужно, на как можно более низком уровне, соедините их вместе, а затем начните все тиканье с помощью одного вызова.
В результате у вас есть отдельное место для решения того, что связано с чем-то, и другое место для управления тем, что говорит о чем-либо.
Попробуйте это на чем-то, что вы должны поддерживать со временем, и посмотрите, не поможет ли это.
источник
Как клиент, когда вы нанимаете механика, который будет что-то делать с вашим автомобилем, ожидаете ли вы, что этот механик будет строить автомобиль с нуля только для того, чтобы потом с ним работать? Нет, вы предоставляете механику автомобиль, на котором вы хотите, чтобы он работал .
Как владелец гаража, когда вы поручаете механику что-то делать с автомобилем, ожидаете ли вы, что механик создаст свою собственную отвертку / гаечный ключ / детали автомобиля? Нет, вы предоставляете механику необходимые ему детали / инструменты
Почему мы это делаем? Мы подумаем. Вы владелец гаража, который хочет нанять кого-то, чтобы стать вашим механиком. Вы научите их быть механиком (= вы напишите код).
Что будет проще:
Есть огромные преимущества, если ваш механик не создает все с нуля:
Если вы будете нанимать и обучать специалиста-механика, то в итоге вы получите сотрудника, который стоит дороже, требует больше времени для выполнения простой работы и постоянно нуждается в переподготовке, когда требуется одна из их многочисленных обязанностей. быть обновленным.
Аналогия с разработкой заключается в том, что если вы используете классы с жестко запрограммированными зависимостями, то в итоге у вас возникнут сложные для обслуживания классы, которые будут нуждаться в постоянной переработке / изменениях всякий раз, когда создается новая версия объекта (
Settings
в вашем случае), и вы Придется разработать внутреннюю логику, чтобы у класса была возможность создавать разные типыSettings
объектов.Кроме того, тот, кто потребляет ваш класс, теперь должен будет попросить класс создать правильный
Settings
объект, а не просто передавать классу любойSettings
объект, который он хочет передать. Это означает дополнительную разработку для потребителя, чтобы выяснить, как попросить класс создать правильный инструмент.Да, инверсия зависимостей требует немного больше усилий для написания, чем жесткое кодирование зависимости. Да, раздражает, когда приходится больше печатать.
Но это тот же аргумент, что и выбор жесткого кодирования литеральных значений, потому что «объявление переменных требует больше усилий». Технически правильно, но профи перевешивают минусы на несколько порядков .
Преимущество инверсии зависимостей не ощущается при создании первой версии приложения. Преимущество инверсии зависимостей возникает, когда вам нужно изменить или расширить эту первоначальную версию. И не обманывайте себя, думая, что вы поймете это правильно с первого раза и вам не нужно будет расширять / изменять код. Вам придется изменить вещи.
Это не влияет на производительность приложения во время выполнения. Но это сильно влияет на время разработки (и, следовательно, производительность) разработчика .
источник
Как и все шаблоны, очень важно спросить «почему», чтобы избежать раздутых дизайнов.
Для внедрения зависимости это легко увидеть, если подумать о двух, пожалуй, наиболее важных аспектах проектирования ООП ...
Низкая связь
Связь в компьютерном программировании :
Вы хотите добиться низкого сцепления. Сильная связь двух вещей означает, что если вы измените одну, вам, скорее всего, придется изменить другую. Ошибки или ограничения в одном могут вызвать ошибки / ограничения в другом; и так далее.
Один класс, создающий объекты для других, является очень сильной связью, потому что один должен знать о существовании другого; он должен знать, как создать его экземпляр (какие аргументы нужны конструктору), и эти аргументы должны быть доступны при вызове конструктора. Кроме того, в зависимости от того, нуждается ли язык в явной деконструкции (C ++), это приведет к дополнительным сложностям. Если вы вводите новые классы (то есть, a
NextSettings
или что-то еще), вы должны вернуться к исходному классу и добавить дополнительные вызовы к их конструкторам.Высокая сплоченность
Сплоченность :
Это другая сторона медали. Если вы посмотрите на одну единицу кода (один метод, один класс, один пакет и т. Д.), Вы захотите, чтобы весь код внутри этой единицы имел как можно меньше обязанностей.
Основным примером для этого может служить шаблон MVC: вы четко отделяете модель предметной области от представления (GUI) и управляющий слой, который склеивает их вместе.
Это позволяет избежать раздувания кода, когда вы получаете большие куски, которые делают много разных вещей; если вы хотите изменить какую-то его часть, вы должны отслеживать и все другие функции, пытаясь избежать ошибок и т. д .; и вы быстро запрограммируете себя в дыру, из которой очень трудно выбраться.
С внедрением зависимостей вы делегируете создание или отслеживание зависимостей любым классам (или файлам конфигурации), которые реализуют ваш DI. Другие классы не будут особо заботиться о том, что именно происходит - они будут работать с некоторыми универсальными интерфейсами и не будут знать, какова реальная реализация, что означает, что они не несут ответственности за другие вещи.
источник
Вы должны использовать методы для решения проблем, которые они хорошо решают, когда у вас есть эти проблемы. Инверсия зависимости и впрыск ничем не отличаются.
Инверсия или внедрение зависимостей - это метод, который позволяет вашему коду решать, какая реализация метода вызывается во время выполнения. Это максимизирует преимущества позднего связывания. Техника необходима, когда язык не поддерживает замену неэкземплярных функций во время выполнения. Например, в Java отсутствует механизм для замены вызовов статического метода вызовами другой реализации; в отличие от Python, где все, что необходимо для замены вызова функции, - это привязать имя к другой функции (переназначить переменную, содержащую функцию).
Почему мы хотим варьировать реализацию функции? Есть две основные причины:
Вы также можете принять к сведению инверсию контрольных контейнеров. Это метод, предназначенный для того, чтобы помочь вам избежать огромных, запутанных конструкционных деревьев, которые выглядят так:
Это позволяет вам зарегистрировать ваши классы, а затем делает конструкцию для вас:
Обратите внимание, что проще всего, если зарегистрированные классы могут быть одиночными без состояния .
Слово предостережения
Обратите внимание, что инверсия зависимостей не должна быть вашим основным ответом для логики развязки. Ищите возможности использовать параметризацию вместо этого. Рассмотрим этот метод псевдокода, например:
Мы могли бы использовать инверсию зависимостей для некоторых частей этого метода:
Но мы не должны, по крайней мере, не полностью. Обратите внимание, что мы создали класс с состоянием
Querier
. Теперь он содержит ссылку на некоторый по существу глобальный объект соединения. Это создает проблемы, такие как трудности в понимании общего состояния программы и того, как разные классы координируются друг с другом. Также обратите внимание, что мы вынуждены выдумывать запрос или соединение, если хотим проверить логику усреднения. Кроме того, лучшим подходом было бы увеличение параметризации :И соединение будет управляться на каком-то еще более высоком уровне, который отвечает за работу в целом и знает, что делать с этим выводом.
Теперь мы можем проверить логику усреднения полностью независимо от запросов, и, более того, мы можем использовать ее в более широком диапазоне ситуаций. Мы могли бы подвергнуть сомнению , даже ли нам нужны
MyQuerier
иAverager
объекты, а может быть , ответ в том , что мы не делаем , если мы не намерены модульное тестированиеStuffDoer
, а не модульное тестированиеStuffDoer
было бы вполне разумно , так как он так тесно связан с базой данных. Возможно, имеет смысл просто позволить интеграционным тестам охватить это. В этом случае, мы могли бы хорошо работатьfetchAboveMin
иaverageData
в статических методах.источник