пятница, 17 декабря 2010 г.

Service Locator - паттерн или антипаттерн?

В предыдущей статье про одно из нововведений в ASP.NET MVC 3 RC я в заключении упомянул про мнение по поводу паттерна Service Locator, вот ссылка на тот самый коммент Марка Симонна, а в нём есть ссылка на статью, где разбирается означенный вопрос. Я не буду дословно переводить всю статью, но попробую описать мысли из статьи и свои собственные, на которые меня этот материал навёл.

Паттерн Service Locator

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

Надо сказать, что тот же Unity от MS реализует как раз этот паттерн, да и в основе почти всех IoC-контейнеров будет наверняка лежать он же.

Service Locator - антипаттерн

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

В этом я согласен с Марком, и ошибки имеют место и отследить зависимости в большом проекте становится не так просто, как перейти прямо к определению типа в среде разработки.

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

Так всё таки паттерн или антипаттерн?

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

А "правильно" надо было сделать так, что зависимости от интерфейсов отражаются в конструкторе класса, а сам класс ничего и знать не знает о сервис-локаторе (это всё по возможности, конечно). После этого и тестируемость класса повышается: пропадает необходимость при тестировании ещё и сам локатор использовать, все тестовые мок-объекты передаются через конструктор.

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

В общем, даже если у вас в руках молоток, не стоит в каждой проблеме видеть только гвоздь (а вдруг это гайка? :) ).

P.S.
Есть ещё одна статейка на хабре на эту тему, тоже советую почитать: Использование IoC контейнеров. За и Против

Progg it