В чем разница между шаблонами Dependency Injection и Service Locator? - PullRequest
260 голосов
/ 13 октября 2009

Обе модели кажутся реализацией принципа инверсии управления. То есть объект не должен знать, как построить свои зависимости.

Внедрение зависимостей (DI), похоже, использует конструктор или установщик для "внедрения" своих зависимостей.

Пример использования конструктора Injection:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Сервисный локатор, похоже, использует «контейнер», который связывает его зависимости и выдает его в виде бара.

Пример использования сервисного локатора:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}

Поскольку наши зависимости - это просто сами объекты, эти зависимости имеют зависимости, которые имеют еще больше зависимостей, и так далее, и тому подобное. Таким образом, Инверсия Контейнера Контроля (или Контейнера DI) родилась. Примеры: Замок Виндзор, Нинъект, Карта структуры, Весна и т. Д.)

Но Контейнер IOC / DI выглядит точно как Локатор Сервиса. Называет ли это контейнер DI плохим именем? Является ли контейнер IOC / DI просто еще одним типом сервисного локатора? Есть ли нюанс в том, что мы используем DI-контейнеры в основном, когда у нас много зависимостей?

Ответы [ 13 ]

158 голосов
/ 13 октября 2009

Разница может показаться незначительной, но даже с ServiceLocator класс по-прежнему отвечает за создание своих зависимостей. Это просто использует сервисный локатор, чтобы сделать это. С DI класс получает свои зависимости. Он не знает и не заботится о том, откуда они берутся. Одним из важных результатов этого является то, что пример DI гораздо проще для модульного тестирования - потому что вы можете передать ему ложные реализации его зависимых объектов. Вы могли бы объединить два - и ввести сервисный локатор (или фабрику), если хотите.

79 голосов
/ 13 октября 2009

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

Хорошее сравнение: http://martinfowler.com/articles/injection.html

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

42 голосов
/ 13 октября 2009

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

Более того, локаторы служб нарушают инкапсуляцию, поскольку они предоставляют глобальную точку доступа к зависимостям других объектов. С сервисным локатором, как с любым синглтоном :

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

При внедрении зависимостей после определения зависимостей объекта они контролируются самим объектом.

33 голосов
/ 24 апреля 2012

Мартин Фаулер заявляет :

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

Вкратце: Service Locator и Dependency Injection являются просто реализациями принципа инверсии зависимости.

Важным принципом является «Зависимость от абстракций, а не от конкреций». Это сделает ваш программный дизайн «слабосвязанным», «расширяемым», «гибким».

Вы можете использовать тот, который наилучшим образом соответствует вашим потребностям. Для большого приложения, имеющего огромную кодовую базу, лучше использовать Service Locator, потому что Dependency Injection потребует больше изменений в вашей кодовой базе.

Вы можете проверить это сообщение: Инверсия зависимостей: поиск сервисов или внедрение зависимостей

Также классика: Инверсия управляющих контейнеров и шаблон внедрения зависимостей. Автор Martin Fowler

Проектирование многоразовых классов Ральф Э. Джонсон и Брайан Фут

Однако, тот, который открыл мне глаза, был: ASP.NET MVC: разрешить или ввести? Это проблема ... Дино Эспозито

20 голосов
/ 14 октября 2009

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

DI и SL могут работать вместе. Полезно иметь центральное расположение для общих зависимостей (например, настройки, регистратор и т. Д.). Учитывая класс, использующий такие deps, вы можете создать «настоящий» конструктор, который получает deps, и конструктор по умолчанию (без параметров), который извлекает из SL и пересылает в «настоящий» конструктор.

РЕДАКТИРОВАТЬ: и, конечно, когда вы используете SL, вы вводите некоторую связь с этим компонентом. Что иронично, поскольку идея такой функциональности заключается в поощрении абстракций и уменьшении связи. Проблемы могут быть сбалансированы, и это зависит от того, сколько мест вам нужно будет использовать SL. Если все сделано как предложено выше, просто в конструкторе класса по умолчанию.

6 голосов
/ 03 августа 2015

В моем последнем проекте я использую оба. Я использую внедрение зависимостей для юнит тестируемости. Я использую сервисный локатор, чтобы скрыть реализацию и зависеть от моего контейнера IoC. и да! Как только вы используете один из контейнеров IoC (Unity, Ninject, Windsor Castle), вы зависите от него. И как только он устареет или по какой-то причине, если вы захотите поменять его местами, вам придется / может потребоваться изменить свою реализацию - по крайней мере, композицию root. Но сервисный локатор абстрагирует эту фазу.

Как бы вы не зависели от вашего контейнера IoC? Либо вам нужно будет обернуть его самостоятельно (что является плохой идеей), либо вы используете Service Locator для настройки контейнера IoC. Таким образом, вы сообщите локатору службы, чтобы получить какой интерфейс вам нужен, и он вызовет контейнер IoC, настроенный для получения этого интерфейса.

В моем случае я использую ServiceLocator , который является компонентом фреймворка. И используйте Unity для контейнера IoC. Если в будущем мне нужно поменять свой контейнер IoC на Ninject , все, что мне нужно сделать, - это настроить мой локатор служб для использования Ninject вместо Unity. Простая миграция.

Вот отличная статья, объясняющая этот сценарий; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/

5 голосов
/ 23 февраля 2015

Оба они являются методами реализации IoC. Есть и другие шаблоны, которые реализуют Inversion of Control:

  • заводская модель
  • сервисный локатор
  • внедрение зависимости (Внедрение в конструктор, Внедрение параметров (если не требуется), Внедрение сеттера в интерфейсное внедрение) ...

Сервисный локатор и DI кажутся более похожими, оба они используют контейнер для определения зависимостей, которые сопоставляют абстракцию с конкретной реализацией.

Основное различие заключается в том, как расположены зависимости: в клиентском коде Service Location запрашиваются зависимости, в DI мы используем контейнер для создания всех объектов, и он вводит зависимость в качестве параметров (или свойств) конструктора.

5 голосов
/ 03 июля 2012

Одна причина для добавления, вдохновленная обновлением документации, которое мы написали для проекта MEF на прошлой неделе (я помогаю строить MEF).

Как только приложение составлено из потенциально тысяч компонентов, может быть трудно определить, может ли конкретный компонент быть правильно создан. Под «созданным правильно» я подразумеваю, что в этом примере, основанном на компоненте Foo, экземпляр IBar и будет доступен, и что компонент, обеспечивающий его, будет:

  • имеет необходимые зависимости,
  • не участвовать в каких-либо недопустимых циклах зависимости, а
  • в случае MEF предоставляется только с одним экземпляром.

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

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

Корень этой проблемы - различие, уже вызванное @Jon: внедрение зависимостей через конструктор является декларативным, а во второй версии используется обязательный шаблон Service Locator.

Контейнер IoC, при аккуратном использовании, может статически анализировать конфигурацию среды выполнения вашего приложения, фактически не создавая экземпляров задействованных компонентов. Многие популярные контейнеры предоставляют некоторые варианты этого; Microsoft.Composition , версия MEF для веб-приложений и приложений в стиле Metro .NET 4.5, предоставляет образец CompositionAssert в вики-документации. Используя его, вы можете написать код вроде:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);

(см. этот пример ).

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

Надеюсь, это интересное дополнение к этому всестороннему набору ответов по теме!

5 голосов
/ 19 февраля 2011

Я думаю, что оба работают вместе.

Внедрение зависимостей означает, что вы помещаете некоторый зависимый класс / интерфейс в потребляющий класс (обычно в его конструктор). Это разъединяет два класса через интерфейс и означает, что потребляющий класс может работать со многими типами реализаций «внедренной зависимости».

Роль сервисного локатора - собрать вашу реализацию. Вы устанавливаете сервисный локатор через некоторое загрузочное связывание в начале вашей программы. Начальная загрузка - это процесс привязки типа реализации к конкретному абстрактному / интерфейсу. Который создается для вас во время выполнения. (в зависимости от вашей конфигурации или начальной загрузки). Если бы вы не внедрили внедрение зависимостей, было бы очень трудно использовать указатель службы или контейнер IOC.

3 голосов
/ 02 апреля 2013

В этом упрощенном случае нет никакой разницы, и они могут использоваться взаимозаменяемо. Однако реальные проблемы не так просты. Просто предположим, что у самого класса Bar есть другая зависимость с именем D. В этом случае ваш локатор службы не сможет разрешить эту зависимость, и вам придется создать его экземпляр в классе D; потому что ваши классы несут ответственность за создание их зависимостей. Было бы еще хуже, если бы у самого класса D были другие зависимости, а в реальных ситуациях это обычно становится еще сложнее. В таких случаях DI является лучшим решением, чем ServiceLocator.

...