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

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

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

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

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

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

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

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

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

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

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

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

Ответы [ 2 ]

13 голосов
/ 20 августа 2010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5 голосов
/ 19 августа 2010

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

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

Результаты:

Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds

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

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

Сценарий:

$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

...