Какая разница между шаблонами зависимостей зависимостей и Locator?

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

Инъекция зависимостей (DI), по-видимому, использует конструктор или сеттер для "инъекции" его зависимостей.

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

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

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

  //...
}

Локатор сервисов, похоже, использует "контейнер", который связывает свои зависимости и дает foo it bar.

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

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

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

  //...
}

Поскольку наши зависимости - это только сами объекты, эти зависимости имеют зависимости, которые имеют еще больше зависимостей и т.д. и т.д. Таким образом, возникла инверсия контрольного контейнера (или контейнера DI). Примеры: Castle Windsor, Ninject, Карта структуры, Spring и т.д.)

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

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

Мартин Фаулер утверждает:

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

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

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

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

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

Также классика: Инверсия контрольных контейнеров и шаблон впрыска зависимостей Мартина Фаулера

Проектирование многоразовых классов от Ralph E. Johnson и Brian Foote

Однако тот, который открыл мои глаза, был: ASP.NET MVC: разрешить или внедрить? Thats the Issue... by Dino Esposito

Ответ 5

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

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

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

Ответ 6

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

Как вы не будете зависеть от своего контейнера 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/

Ответ 7

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

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

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

Ответ 8

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

  • factory pattern
  • локатор сервисов
  • инъекция зависимостей (впрыск конструктора, впрыск параметров (если это не требуется), инжектор инжектора ввода интерфейса) ...

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

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

Ответ 9

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

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

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

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

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

Корень этой проблемы - это различие, уже вызванное @Jon: вложение зависимостей через конструктор является декларативным, а вторая версия использует императивный шаблон 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);

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

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

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

Ответ 10

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

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

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

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

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

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

Для записи

//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.