В чем разница между шаблонами Dependency Injection и Service Locator?

304

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

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

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

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

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

  //...
}

Сервисный локатор, по-видимому, использует «контейнер», который связывает свои зависимости и дает foo его 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
Инверсия управления означает, что «объект не должен знать, как построить свои зависимости»?!? Этот новый для меня. Нет, правда, это не то, что означает «инверсия контроля». См. Martinfowler.com/bliki/InversionOfControl.html В этой статье даже есть ссылки на этимологию этого термина, начиная с 1980-х годов.
Rogério
1
Ответ здесь: infoq.com/articles/Succeeding-Dependency-Injection
Марк
1
Марк Симанн утверждает, что Service Locator является анти-паттерном ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). Кроме того, я нашел диаграмму (найденную здесь, stackoverflow.com/a/9503612/1977871 ) полезной для понимания затруднений DI и SL. Надеюсь это поможет.
VivekDev

Ответы:

181

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

tvanfosson
источник
20
Кроме того, вы можете использовать как при создании класса. Конструктор по умолчанию может использовать SL для извлечения зависимостей и передачи их «реальному» конструктору, который получает эти зависимости. Вы получаете лучшее из обоих миров.
Грант Пэйлин
6
Нет, ServiceLocator является ответственным за создание правильной реализации для данной зависимости (плагин). В случае DI ответственность за это несет «контейнер» DI.
Rogério
5
@Rogerio да, но класс все еще должен знать о Service Locator ... это две зависимости. Также чаще всего я видел делегирование Service Locator в контейнер DI для поиска, особенно для временных объектов, которые нуждаются в поддержке сервиса.
Адам Гент
2
@ Adam Я не говорил, что Service Locator будет делегировать контейнеру DI. Это два взаимоисключающих паттерна, как описано в «официальной» статье . Для меня Service Locator имеет огромное преимущество перед DI на практике: использование контейнера DI порождает злоупотребления (что я неоднократно видел), а использование Service Locator - нет.
Rogério
3
«Одним из важных результатов этого является то, что пример DI гораздо проще для модульного тестирования - потому что вы можете передать ему ложные реализации его зависимых объектов». Не правда. В ваших модульных тестах можно использовать вызов функции регистра в контейнере локатора службы, чтобы легко добавлять макеты в реестр.
Драмбег
93

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

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

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

Joel
источник
17
Но как вы справляетесь со случаем, когда вам нужно создавать объекты во время выполнения? Если вы создаете их вручную с помощью «new», вы не можете использовать DI. Если вы вызываете DI-фреймворк за помощью, вы нарушаете схему. Так какие варианты остались?
Борис
9
@ Борис У меня была такая же проблема, и я решил ввести фабрики для конкретных классов. Не красиво, но сделал работу. Хотелось бы увидеть более красивое решение.
Чарли Руденстол
Прямая ссылка сравнения: martinfowler.com/articles/...
Теоман shipahi
2
@ Борис Если бы мне нужно было создавать новые объекты на лету, я бы добавлял абстрактную фабрику для указанных объектов. Это было бы похоже на внедрение сервисного локатора в этом случае, но обеспечивает конкретный, унифицированный интерфейс времени компиляции для построения соответствующих объектов и делает зависимости явными.
LivePastTheEnd
51

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

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

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

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

Джефф Стерн
источник
3
Я предпочитаю «Синглтоны считаются глупыми
Чарльз Грэм,
2
Я люблю старого Стива Йегге, и заголовок этой статьи великолепен, но я думаю, что цитируемая мной статья и «Синглтоны - патологические лжецы» Мишко Хевери ( misko.hevery.com/2008/08/17/singletons-are-pathological- лжецы ) лучше рассуждать против особой чертовщины сервисного локатора.
Джефф Стерн
Этот ответ является наиболее правильным, потому что он лучше всего определяет локатор службы: «Класс, который скрывает свои зависимости». Обратите внимание, что внутреннее создание зависимости, хотя и не очень хорошая вещь, не делает класс локатором службы. Кроме того, получение зависимости от контейнера является проблемой, но не «проблемой», которая наиболее четко определяет локатор службы.
Сэм
1
With dependency injection (at least constructor injection) the dependencies are explicit., Пожалуйста, объясни.
FindOutIslamNow
Как и выше, я не могу понять, как SL делает зависимости менее явными, чем DI ...
Михал Повлока
38

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

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

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

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

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

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

Также классика: Инверсия Контейнеров Контроля и Шаблон Внедрения Зависимостей. Автор Martin Fowler

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

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

Натан
источник
Фантастическое резюме: «Сервисный локатор и внедрение зависимостей - просто реализации принципа инверсии зависимостей».
Ганс,
И он также заявляет: ключевое отличие состоит в том, что с локатором службы каждый пользователь службы имеет зависимость от локатора. Локатор может скрывать зависимости от других реализаций, но вам нужно видеть локатор. Таким образом, решение между локатором и инжектором зависит от того, является ли эта зависимость проблемой.
программы
1
ServiceLocator и DI не имеют ничего общего с «Принципом инверсии зависимостей» (DIP). DIP - это способ сделать высокоуровневый компонент более пригодным для повторного использования, заменив зависимость времени компиляции от низкоуровневого компонента зависимостью от абстрактного типа, определенного вместе с высокоуровневым компонентом, который реализуется низкоуровневым компонентом. компонент уровня; таким образом, зависимость времени компиляции инвертируется, так как теперь это низкоуровневая зависимость, которая зависит от высокоуровневой. Также обратите внимание, что в статье Мартина Фаулера объясняется, что DI и IoC - это не одно и то же.
Рожерио
23

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

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

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

Грант Пэйлин
источник
Интересный! Я использую DI и SL, но не с двумя конструкторами. Три или четыре наиболее скучных часто необходимых зависимости (настройки и т. Д.) Получаются из SL на лету. Все остальное вводится конструктором. Это немного некрасиво, но прагматично.
Maaartinus
10

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

  • Фабричный образец
  • Сервисный локатор
  • DI (IoC) Контейнер
  • Инжекция зависимостей (инжекция конструктора, инжекция параметров (если не требуется), инъекция сеттера в инжекцию интерфейса) ...

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

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

Nininea
источник
7

В моем последнем проекте я использую оба. Я использую инъекцию зависимостей для модульной тестируемости. Я использую сервисный локатор, чтобы скрыть реализацию и зависимость от моего контейнера 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/

Теоман Шипахи
источник
Ссылка на статью в Johandekoning не работает.
JakeJ
6

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

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

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

NoelAdy
источник
6

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

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

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

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

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

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

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

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

CompositionAssert.CanExportSingle<Foo>(container);

(См. Этот пример ).

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

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

Николас Блумхардт
источник
5

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

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

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

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

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

ИМХО, называя его «локатором» вместо «местоположения» или «определения местоположения», можно также иногда думать, что локатор службы в статье ссылается на контейнер локатора службы, а не на шаблон локатора службы (анти-) особенно когда есть связанный паттерн, который называется Dependency Injection, а не Dependency Injector.

blizpasta
источник
4

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

Даниил
источник
4
Хм, я бы не согласился: сервис локатор отл. ясно показывает, что там все еще есть зависимость ... локатор службы. Если у самого barкласса есть зависимость, у него barтакже будет локатор службы, в этом весь смысл использования DI / IoC.
GFoley83
2

В чем разница (если есть) между внедрением зависимостей и поиском сервисов? Оба шаблона хороши в реализации принципа инверсии зависимости. Шаблон Service Locator легче использовать в существующей кодовой базе, поскольку он делает общий дизайн более свободным, не вызывая изменений в общедоступном интерфейсе. По этой же причине код, основанный на шаблоне локатора службы, менее читаем, чем эквивалентный код, основанный на внедрении зависимостей.

Паттерн Dependency Injection проясняет, так как сигнатура имеет зависимости, которые будут иметь класс (или метод). По этой причине полученный код становится чище и более читабельным.

Yogesh
источник
1

Следующая простая концепция дала мне более четкое понимание различий между Service Locator и DI Container:

  • Сервисный локатор используется потребителем и извлекает сервисы по идентификатору из некоторого хранилища по прямому запросу потребителя.

  • Контейнер DI находится где-то снаружи, он берет сервисы из некоторого хранилища и передает их потребителю (независимо от того, конструктор или метод)

Тем не менее, мы можем говорить о различии между ними только в контексте конкретного использования потребителем. При использовании Service Locator и DI Container в корне композиции они почти одинаковы.

Римский
источник
0

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

Гленн Мохаммед
источник
-2

Для записи

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

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

  //...
}

Если вам действительно не нужен интерфейс (интерфейс используется более чем одним классом), вы НЕ ДОЛЖНЫ ИСПОЛЬЗОВАТЬ его . В этом случае IBar позволяет использовать любой класс обслуживания, который его реализует. Однако обычно этот интерфейс будет использоваться одним классом.

Почему плохая идея использовать интерфейс? Потому что это действительно сложно отлаживать.

Например, допустим, что экземпляр «bar» не удался, вопрос: какой класс не удался? Какой код я должен исправить? Простое представление, оно ведет к интерфейсу, и именно здесь заканчивается моя дорога.

Вместо этого, если код использует жесткую зависимость, легко отладить ошибку.

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

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

  //...
}

Если «bar» не срабатывает, я должен проверить и запустить класс BarService.

Магеллана
источник
1
Класс - это план для создания конкретного объекта. С другой стороны, интерфейс является a contractи просто определяет поведение, а не действие. Вместо того, чтобы обойти реальный объект, только интерфейс используется совместно, чтобы потребитель не получил доступ к остальной части вашего объекта. Также для модульного тестирования это помогает протестировать только ту часть, которая должна быть проверена. Думаю, со временем вы поймете его полезность.
Ганхан