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

Недавно я прочитал статью Mark Seemann об анти-шаблоне Locator Service Locator.

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

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

  • Проблема с обслуживанием (что меня озадачивает)
    Рассмотрим следующий пример

У нас есть класс "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();
    }
}

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

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

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

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

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

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

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

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

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

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

Ответ 1

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

Вместо этого нам нужно посмотреть, имеются ли правильные способы использования шаблонов, а для 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?

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Проблема с обслуживанием (что меня озадачивает)

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

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

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

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

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

У Mark Seemann есть ответ fooobar.com/questions/31030/..., что применяется не только при использовании локатора сервисов из другой библиотеки, но также при использовании внешних по умолчанию в службах.

Ответ 5

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

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

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

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

Ответ 6

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

Вот параграф Adaptive Code via С#:

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

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