Сервис локатор – это очень неоднозначный паттерн проектирования. С одной стороны, именно с его появлением началось активное развитие 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) {}    }

И хотя использование Сервис Локатора является довольно популярным, у него есть ряд фатальных существенных недостатков.