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



Answers

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

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

Question

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

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

  1. Проблема использования API (с которой я отлично справляюсь)
    Когда класс использует локатор службы, очень сложно увидеть его зависимости, так как в большинстве случаев класс имеет только один конструктор 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();
    }
}

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

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

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

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

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

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

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

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

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

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




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

Вот параграф из Adaptive Code via C # :

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

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




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

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

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

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

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

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

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




Related