ServiceLocator-это анти-паттерн?

недавно я читал Марк Зееман-х о сервисном локаторе anti-pattern.

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

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

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

у нас есть класс '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 анти-шаблоном" действительно зависит от обстоятельств. И я определенно не предложил бы вычеркнуть его из вашего списка инструментов. Это может стать очень удобным, когда вы начнете иметь дело с legacy код. Если вам посчастливилось быть в самом начале вашего проекта, то подход DI может быть лучшим выбором, поскольку он имеет некоторые преимущества перед Service Locator.

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

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

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

6 ответов


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

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

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


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

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


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


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

есть 2 различные причины, почему использование service locator плохо в этом отношении.

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

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

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

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

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


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

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

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

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


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

вот абзац из Адаптивный Код На C#:

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

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