Что лучше: внедрение зависимостей + реестр или внедрение зависимостей или глобальный реестр?

Во-первых, я хочу ограничить этот вопрос только веб-разработкой. Так что это не зависит от языка, пока язык используется для веб-разработки. Лично я исхожу из опыта работы с PHP.

Часто нам нужно использовать объект из нескольких областей видимости. Например, нам может потребоваться использовать класс базы данных в обычной области видимости, но затем также из класса контроллера. Если мы создадим объект базы данных в нормальной области видимости, мы не сможем получить к нему доступ изнутри класса контроллера. Мы хотим избежать создания двух объектов базы данных в разных областях, поэтому нам нужен способ повторного использования класса базы данных независимо от области действия. Для этого у нас есть два варианта:

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

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

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

Лично я использую второй подход из-за множества статей, в которых его пропагандируют вместо использования глобальных переменных, но я столкнулся с двумя проблемами. Во-первых, класс реестра будет содержать огромное количество рекурсии. Например, класс реестра будет содержать переменные входа в базу данных, необходимые классу базы данных. Следовательно, нам нужно внедрить класс реестра в базу данных. Однако база данных понадобится многим другим классам, поэтому ее нужно будет добавить в реестр, создав цикл. Могут ли современные языки справиться с этим нормально или это вызывает огромные проблемы с производительностью? Обратите внимание, что глобальный реестр от этого не страдает, поскольку он ни во что не передается.

Во-вторых, я начну передавать большие объемы данных объектам, которым они не нужны.Моя база данных не заботится о моем маршрутизаторе, но маршрутизатор будет передан в базу данных вместе с деталями подключения к базе данных. Это усугубляется проблемой рекурсии, потому что, если у маршрутизатора есть реестр, реестр имеет базу данных и реестр, и реестр передается в базу данных, тогда база данных передается самой себе через маршрутизатор (т. Е. Я мог бы сделать $ this-> registry-> router-> registry-> database из класса базы данных`).

Более того, я не вижу того, что DI дает мне, кроме большей сложности. Мне нужно передать дополнительную переменную в каждый объект, и я должен использовать объекты реестра с $ this-> registry-> object-> method () вместо $ registry-> object-> method () . Очевидно, что это не серьезная проблема, но она кажется ненужной, если это не дает мне ничего сверх глобального подхода.

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

С учетом этих проблем с обеими версиями DI, разве не превосходит глобальный реестр? Что я теряю, используя глобальный реестр вместо DI?

Одна вещь, которая часто упоминается при обсуждении DI vs Globals, заключается в том, что глобальные переменные препятствуют вашей способности правильно тестировать вашу программу. Как именно глобальные переменные мешают мне тестировать программу, в которой я бы не стал? Я читал во многих местах, что это связано с тем, что глобальное значение можно изменить откуда угодно, и поэтому его сложно высмеять.Однако мне кажется, что, поскольку, по крайней мере, в PHP объекты передаются по ссылке, изменение внедренного объекта в некотором классе также изменит его в любом другом классе, в который он был внедрен.

16
задан Rupert Madden-Abbott 19 August 2010 в 19:00
поделиться

2 ответа

Давайте разберемся с этим по одному.

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

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

Обратите внимание, что глобальный реестр не страдает от этого, так как он ни в что не передается.

Для самого Реестра может отсутствовать рекурсия, но объекты в Реестре вполне могут иметь циклические ссылки. Это потенциально может привести к утечке памяти при отмене установки объектов из реестра с версиями PHP до 5.3, когда Сборщик мусора не будет собирать их должным образом.

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

Истинный. Но именно для этого и предназначен Реестр. Это не сильно отличается от передачи _GLOBALS долларов в ваши объекты. Если вы этого не хотите, не используйте реестр, но передают только аргументы, необходимые для того, чтобы экземпляры класса находились в допустимом состоянии. Или просто не храните его.

Я мог бы сделать $this->registry->router->registry->database

Маловероятно, что маршрутизатор предоставляет открытый метод для получения реестра. Вы не сможете получить доступ к database из $this через router, но вы сможете получить доступ к database напрямую. Конечно. Это реестр. Вот для чего вы его написали. Если требуется сохранить реестр в объектах, их можно обернуть в раздельный интерфейс, который разрешает доступ только к подмножеству данных, содержащихся в нем.

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

Не обязательно. При использовании впрыска конструктора, можно ограничить количество аргументов аргументами, абсолютно необходимыми для перевода объекта в допустимое состояние. Остальные необязательные зависимости также могут быть установлены с помощью внедрения сеттера. Кроме того, никто не мешает вам добавлять аргументы в объект Array или Config. Или используйте Builders.

Учитывая эти проблемы с обеими версиями DI, разве глобальный реестр не превосходит? Что я теряю при использовании глобального реестра через DI?

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

Одна вещь, которая часто упоминается при обсуждении DI против Globals, заключается в том, что глобалы препятствуют вашей способности правильно тестировать вашу программу. Как именно глобалы мешают мне тестировать программу там, где DI этого не делает?

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

Я читал во многих местах, что это связано с тем, что глобальный может быть изменен из любого места и, следовательно, его трудно имитировать

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

13
ответ дан 30 November 2019 в 22:30
поделиться

Я отправлю это как ответ, так как хочу добавить код.

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

Результаты:

Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds

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

Итак, да, похоже, что нет реальной разницы в производительности между этими двумя разными методами передачи данных. Так что сделайте лучший архитектурный выбор (ИМХО, это инъекция зависимостей) ...

Сценарий:

$its = 10000;
$bar = new stdclass();
$bar->foo = 'bar';
$bar->bar = $bar;
$bar->baz = new StdClass();
$bar->baz->ar = 'bart';

$s = microtime(true);
for ($i=0;$i<$its;$i++) passed($bar);
$e = microtime(true);
echo "Passed Completed in ".($e - $s) ." Seconds\n";

$s = microtime(true);
for ($i=0;$i<$its;$i++) globaled();
$e = microtime(true);
echo "Globaled Completed in ".($e - $s) ." Seconds\n";

function passed($bar) {
    is_object($bar);
}

function globaled() {
    global $bar;
    is_object($bar);
}

Протестировано на 5.3.2

5
ответ дан 30 November 2019 в 22:30
поделиться
Другие вопросы по тегам:

Похожие вопросы: