Является ли ServiceLocator антишаблоном?

137

Недавно я прочитал статью Марка Симанна об анти-паттерне Service Locator.

Автор указывает на две основные причины, почему ServiceLocator является анти-паттерном:

  1. Проблема использования API (с которой у меня все в порядке)
    Когда в классе используется локатор Service, очень трудно увидеть его зависимости, поскольку в большинстве случаев у класса есть только один конструктор PARAMETERLESS. В отличие от ServiceLocator, подход DI явно раскрывает зависимости через параметры конструктора, поэтому зависимости легко увидеть в IntelliSense.

  2. Вопрос обслуживания (который меня озадачивает).
    Рассмотрим следующий пример.

У нас есть класс «MyType», который использует подход локатора службы:

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Теперь мы хотим добавить еще одну зависимость в класс 'MyType'

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

И здесь начинается мое недоразумение. Автор говорит:

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

Но подождите секунду, если бы мы использовали подход DI, мы бы ввели зависимость с другим параметром в конструктор (в случае внедрения конструктора). И проблема все еще будет там. Если мы можем забыть настроить ServiceLocator, тогда мы можем забыть добавить новое отображение в наш контейнер IoC, и подход DI будет иметь ту же проблему во время выполнения.

Также автор упомянул о сложностях модульного тестирования. Но разве у нас не будет проблем с подходом DI? Не нужно ли нам обновить все тесты, которые создавали этот класс? Мы обновим их так, чтобы они передавали новую ложную зависимость, чтобы сделать наш тест компилируемым. И я не вижу никакой пользы от этого обновления и затрат времени.

Я не пытаюсь защищать подход Service Locator. Но это недоразумение заставляет меня думать, что я теряю что-то очень важное. Может ли кто-нибудь развеять мои сомнения?

ОБНОВЛЕНИЕ (РЕЗЮМЕ):

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

И вот основные отличия, которые убедили меня не использовать Service Locator для моих новых проектов:

  • Самое очевидное и важное: Service Locator скрывает зависимости классов
  • Если вы используете какой-либо контейнер IoC, он, вероятно, будет сканировать весь конструктор при запуске, чтобы проверить все зависимости и дать вам немедленную обратную связь по отсутствующим сопоставлениям (или неправильной конфигурации); это невозможно, если вы используете свой контейнер IoC в качестве локатора службы

Для подробностей читайте отличные ответы, которые приведены ниже.

Davidoff
источник
«Не нужно ли нам обновить все тесты, которые создавали этот класс?» Не обязательно верно, если вы используете строитель в своих тестах. В этом случае вам нужно будет только обновить сборщик.
Питер Карлссон
Ты прав, это зависит. Например, в больших приложениях для Android до сих пор люди очень неохотно использовали DI из-за проблем с производительностью на мобильных устройствах с низкой спецификацией. В таких случаях вам нужно найти альтернативу написанию тестируемого кода, и я бы сказал, что Service Locator является достаточно хорошей заменой в этом случае. (Примечание: для Android все может измениться, когда новая платформа Dagger 2.0 DI станет достаточно зрелой.)
Г. Ломбард
1
Обратите внимание, что, так как этот вопрос был опубликован, существует обновление в сервисном локаторе Марка Симанна - публикация Anti-Pattern, описывающая, как сервисный локатор нарушает ООП, нарушая инкапсуляцию, что является его лучшим аргументом (и основной причиной для всех симптомов). что он использовал во всех предыдущих аргументах). Обновление 2015-10-26: Основная проблема с Service Locator заключается в том, что он нарушает инкапсуляцию .
NightOwl888

Ответы:

125

Если вы определяете шаблоны как анти-шаблоны только потому, что в некоторых ситуациях они не подходят, тогда ДА - это анти-шаблон. Но с этим рассуждением все паттерны также будут анти-паттернами.

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

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

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

var myType = new MyType();
myType.MyMethod();

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

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Вы можете непосредственно определить зависимости и не можете использовать классы до их удовлетворения.

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

Является ли паттерн анти-паттерном?

Нет.

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

Но гораздо лучшим примером являются ASP.NET MVC и WebApi. Как вы думаете, что делает возможным внедрение зависимостей в контроллерах? Правильно - местоположение сервиса.

Ваши вопросы

Но подождите секунду, если бы мы использовали подход DI, мы бы ввели зависимость с другим параметром в конструктор (в случае внедрения конструктора). И проблема все еще будет там.

Есть еще две серьезные проблемы:

  1. С расположением службы вы также добавляете еще одну зависимость: локатор службы.
  2. Как вы скажете, какое время жизни должны иметь зависимости, и как / когда они должны быть очищены?

С помощью конструктора с использованием контейнера вы получаете это бесплатно.

Если мы можем забыть настроить ServiceLocator, тогда мы можем забыть добавить новое отображение в наш контейнер IoC, и подход DI будет иметь ту же проблему во время выполнения.

Это правда. Но с внедрением конструктора вам не нужно сканировать весь класс, чтобы выяснить, какие зависимости отсутствуют.

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

Также автор упомянул о сложностях модульного тестирования. Но разве у нас не будет проблем с подходом DI?

Нет. Поскольку у вас нет зависимости от статического сервисного локатора. Вы пытались заставить параллельные тесты работать со статическими зависимостями? Это не весело.

jgauffin
источник
2
Jgauffin, спасибо за ваш ответ. Вы указали одну важную вещь об автоматической проверке при запуске. Я не думал об этом, и теперь я вижу еще одно преимущество DI. Вы также привели пример: "var myType = new MyType ()". Но я не могу считать его действительным, так как мы никогда не создаем зависимости в реальном приложении (IoC Container делает это для нас все время). То есть: в приложении MVC у нас есть контроллер, который зависит от IMyService и MyServiceImpl, зависит от IMyRepository. Мы никогда не будем создавать экземпляры MyRepository и MyService. Мы получаем экземпляры из параметров Ctor (например, из ServiceLocator) и используем их. Не так ли?
Давидофф
33
Ваш единственный аргумент в пользу того, что Service Locator не является анти-шаблоном, звучит так: «Инверсия контейнеров управления не будет работать без расположения службы». Этот аргумент, однако, является недопустимым, поскольку Service Locator относится к намерениям, а не к механике, как ясно объяснил здесь Марк Симанн: «Контейнер DI, инкапсулированный в корне композиции, не является Service Locator - это компонент инфраструктуры».
Стивен
4
@jgauffin Web API не использует расположение службы для DI в контроллерах. Это вообще не делает DI. Что он делает: Это дает вам возможность создать свой собственный ControllerActivator для передачи в службы конфигурации. Оттуда вы можете создать корень композиции, будь то Pure DI или Containers. Кроме того, вы смешиваете использование Сервисного местоположения, как компонента вашего шаблона, с определением «Шаблон сервисного локатора». С этим определением Composition Root DI можно рассматривать как «шаблон локатора службы». Таким образом, весь смысл этого определения является спорным.
Суамер
1
Я хочу отметить, что это в целом хороший ответ, я только что прокомментировал один вводящий в заблуждение пункт, который вы высказали.
Суамер
2
@jgauffin DI и SL - версии версий IoC. SL - просто неправильный способ сделать это. Контейнер IoC может быть SL или может использовать DI. Это зависит от того, как он подключен. Но SL это плохо, плохо плохо. Это способ скрыть тот факт, что вы тесно связываете все воедино.
MirroredFate
37

Я также хотел бы отметить, что, если вы проводите рефакторинг устаревшего кода, шаблон Service Locator не только не является анти-шаблоном, но и практической необходимостью. Никто никогда не собирается махать волшебной палочкой за миллионы строк кода, и внезапно весь этот код будет готов к DI. Поэтому, если вы хотите начать вводить DI в существующую кодовую базу, часто бывает так, что вы будете медленно менять положение, чтобы стать сервисами DI, и код, который ссылается на эти сервисы, часто НЕ будет сервисами DI. Следовательно, ЭТИМ сервисам нужно будет использовать сервисный локатор, чтобы получить экземпляры тех сервисов, которые были преобразованы для использования DI.

Таким образом, при рефакторинге больших унаследованных приложений, чтобы начать использовать концепции DI, я бы сказал, что Service Locator не только НЕ является паттерном, но и является единственным способом постепенного применения концепций DI к базе кода.

jwells131313
источник
12
Когда вы имеете дело с устаревшим кодом, все оправдано, чтобы вывести вас из этого беспорядка, даже если это означает выполнение промежуточных (и несовершенных) шагов. Сервисный локатор - это такой промежуточный шаг. Это позволяет вам выйти из этого ада одним шагом за раз, если вы помните, что существует хорошее альтернативное решение, которое задокументировано, воспроизводимо и доказало свою эффективность . Это альтернативное решение - внедрение зависимостей, и именно поэтому сервисный локатор по-прежнему является антипаттерном; правильно разработанное программное обеспечение не использует это это.
Стивен
RE: «Когда вы имеете дело с унаследованным кодом, все оправдано, чтобы вывести вас из этого беспорядка». Иногда я задаюсь вопросом, существует ли когда-либо хоть немного унаследованного кода, но поскольку мы можем оправдать что-либо, чтобы исправить это, мы как-то так и не удалось это сделать.
Дрю Делано
8

С точки зрения тестирования, Service Locator плохой. См. Хорошее объяснение Google Tech Talk Миско Хевери с примерами кода http://youtu.be/RlfLCWKxHJ0, начиная с минуты 8:45. Мне понравилась его аналогия: если вам нужно 25 долларов, просите деньги напрямую, а не отдавайте свой кошелек, откуда деньги будут взяты. Он также сравнивает сервисный локатор с стогом сена, в котором есть нужная игла, и знает, как ее извлечь. Классы, использующие Service Locator, трудно использовать из-за этого.

user3511397
источник
10
Это переработанное мнение и было бы лучше в качестве комментария. Кроме того, я думаю, что ваша (его?) Аналогия служит доказательством того, что некоторые шаблоны более подходят для некоторых проблем, чем другие.
8bitjunkie
6

Вопрос обслуживания (который озадачивает меня)

Есть две разные причины, по которым использование сервисного локатора плохо в этом отношении.

  1. В вашем примере вы жестко кодируете статическую ссылку на локатор службы в своем классе. Это тесно связывает ваш класс непосредственно с локатором службы, что, в свою очередь, означает, что он не будет функционировать без локатора службы . Кроме того, ваши модульные тесты (и любой другой, кто использует класс) также неявно зависят от локатора службы. Одна вещь, которая, похоже, осталась незамеченной, заключается в том, что при использовании инжекции конструктора вам не нужен контейнер DI при модульном тестировании , что значительно упрощает ваши модульные тесты (и способность разработчиков понимать их). Это реальное преимущество модульного тестирования, которое вы получаете от использования инжектора конструктора.
  2. Что касается важности конструктора Intellisense, люди здесь, похоже, полностью упустили этот момент. Класс пишется один раз, но он может использоваться в нескольких приложениях (то есть в нескольких конфигурациях DI) . Со временем это приносит дивиденды, если вы можете взглянуть на определение конструктора, чтобы понять зависимости класса, вместо того, чтобы смотреть на документацию (возможно, современную) или, если это не удастся, вернуться к исходному исходному коду (который может не быть полезным), чтобы определить, каковы классовые зависимости. Класс с локатором службы обычно проще написать , но вы более чем оплачиваете стоимость этого удобства при текущем обслуживании проекта.

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

Рассмотрим случай, когда вам нужно использовать сервис, LibraryAкоторый, как решил его автор, будет использовать, ServiceLocatorAи сервис LibraryB, автор которого решил использовать ServiceLocatorB. У нас нет другого выбора, кроме как использовать 2 разных сервисных локатора в нашем проекте. Сколько зависимостей нужно настроить, это игра в догадки, если у нас нет хорошей документации, исходного кода или автора по быстрому набору. В противном случае нам может понадобиться декомпилятор простовыяснить, каковы зависимости. Возможно, нам потребуется настроить 2 совершенно разных API-интерфейса поиска служб, и в зависимости от дизайна может оказаться невозможным просто обернуть существующий DI-контейнер. Совсем не возможно разделить один экземпляр зависимости между двумя библиотеками. Сложность проекта может быть еще более усложнена, если локаторы сервисов фактически не находятся в тех же библиотеках, что и сервисы, которые нам нужны - мы неявно перетаскиваем дополнительные ссылки на библиотеки в наш проект.

Теперь рассмотрим те же две службы, сделанные с помощью инжектора конструктора. Добавить ссылку на LibraryA. Добавить ссылку на LibraryB. Укажите зависимости в конфигурации DI (анализируя то, что необходимо с помощью Intellisense). Готово.

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

NightOwl888
источник
1

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

Вот параграф из Адаптивного кода через C # :

«К сожалению, локатор службы иногда является неизбежным антишаблоном. В некоторых типах приложений, особенно в Windows Workflow Foundation, инфраструктура не поддается внедрению в конструктор. В этих случаях единственной альтернативой является использование локатора службы. лучше, чем вообще не вводить зависимости. При всех моих проблемах с (анти-) шаблоном это бесконечно лучше, чем построение зависимостей вручную. В конце концов, он по-прежнему включает все важные точки расширения, предоставляемые интерфейсами, которые позволяют декораторам, адаптерам, и аналогичные преимущества. "

- Холл, Гэри Маклин. Адаптивный код через C #: гибкое кодирование с шаблонами проектирования и принципами SOLID (Справочник разработчика) (стр. 309). Пирсон Образование.

Сиаваш Мортазави
источник
0

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

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

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

Вы правы, что у DI есть свои проблемы / недостатки, но упомянутые преимущества намного перевешивают их ... ИМО. Вы правы, что в DI есть зависимость, введенная в интерфейс (конструктор) - но, надеюсь, это та самая зависимость, которая вам нужна и которую вы хотите сделать видимой и проверяемой.

Zrin
источник
Зрин, спасибо за твои мысли. Как я понимаю, при «правильном» подходе DI я не должен создавать экземпляры своих зависимостей нигде, кроме модульных тестов. Таким образом, компилятор поможет мне только с моими тестами. Но, как я описал в своем первоначальном вопросе, эта «помощь» с испорченными тестами мне ничего не дает. Является ли?
Давидофф
Аргумент «статическая информация» / «проверка во время компиляции» - соломенный человек. Как отметил @davidoff, DI одинаково подвержен ошибкам во время выполнения. Я также хотел бы добавить, что современные среды IDE предоставляют всплывающие подсказки для комментариев / сводной информации для участников, и даже в тех, которые этого не делают, кто-то по-прежнему будет смотреть на документацию, пока он просто не «узнает» API. Документация - это документация, будь то обязательный параметр конструктора или примечание о том, как настроить зависимость.
tuespetre
Думая о реализации / кодировании и обеспечении качества - читаемость кода имеет решающее значение - особенно для определения интерфейса. Если вы можете обойтись без автоматических проверок времени компиляции и если вы адекватно комментируете / документируете свой интерфейс, то, я думаю, вы можете хотя бы частично устранить недостатки скрытой зависимости от некоторой глобальной переменной с не легко видимым / предсказуемым содержимым. Я бы сказал, что вам нужны веские причины, чтобы использовать этот шаблон, который перевешивает эти недостатки.
Зрин
0

Да, сервисный локатор является анти-паттерном, он нарушает инкапсуляцию и является солидным .

Алиреза Рахмани Халили
источник
1
Я понимаю, что вы имеете в виду, но было бы полезно добавить больше подробностей, например, почему это нарушает SOLID
reggaeguitar