Сервис локатор – это очень неоднозначный паттерн проектирования. С одной стороны, именно с его появлением началось активное развитие DI контейнеров и практик инверсии управления. С другой стороны, именно благодаря повсеместному использованию сервис локаторов в приложениях техники управления зависимостями получили и продолжают получать столько критики.
Но прежде чем переходить к описанию достоинств и недостатков этого паттерна, давайте дадим его описание и общую структуру.
Суть паттерна Сервис Локатор сводится к тому, что вместо создания конкретных объектов («сервисов») напрямую с помощью ключевого слова new, мы будем использовать специальный «фабричный» объект, который будет отвечать за создание, а точнее «нахождение» всех сервисов.
// Статический "локатор" public static class ServiceLocator { public static object GetService(Type type) {} public static T GetService<T>() {} } // Сервис локатор в виде интерфейса public interface IServiceLocator { T GetService<T>(); }
Сервис локатор может быть статическим классом с набором статических методов, или же может существовать в виде интерфейса для упрощения тестирования.
Разорвать жесткую связь между классом и его вспомогательными сервисами, путем добавления специального класса (локатора), который будет за это отвечать.
Прочитав описание и назначение этого паттерна, внимательный читатель заметит невероятное сходство Сервис Локатора со своим любимым DI контейнером. И правда, основная суть любого контейнера заключается в регистрации и последующего получения требуемых зависимостей, с рядом дополнительных свистелок, которые делают этот процесс более сложным удобным.
Однако между DI контейнером и его использованием в виде Сервис Локатора существует тонкая грань. По всем правилам, использование контейнера должно быть ограничено минимальным количеством мест. В идеале, в приложении должна быть лишь одна точка, где производится вызов метода container.Resolve()
; этот код должен находиться либо в точке инициализации приложения (так называемый Composition Root), либо максимально близко к ней.
Однако наличие в арсенале универсального объекта, способного получить любую зависимость, провоцирует к его использованию напрямую и в других частях приложения.
Предположим, нам во вью модели редактирования карточки сотрудника нам нужно обратиться к сервису из слоя доступа к данным. Мы можем протянуть нужную зависимость через конструктор, а можем просто передать этой вью модели сам контейнер, чтобы она получила требуемую зависимость самостоятельно:
class EditEmployeeViewModel { private Employee _employee; private IServiceLocator _serviceLocator; public EditEmployeeViewModel(IServiceLocator serviceLocator) { _serviceLocator = serviceLocator; } private void OkCommandHandler() { ValidateEmployee(_employee); var repository = _serviceLocator.GetService<IRepository>(); repository.Save(_employee); } private void ValidateEmployee(Employee employee) {} }
И хотя использование Сервис Локатора является довольно популярным, у него есть ряд фатальных существенных недостатков.