Семантика
Локатор служб (или Service Locator) это паттерн, используемый для решения задачи хранения, создания и глобального доступа к сервисам приложения. Он часто применяется не осознанно путем создания “Божественного объекта”, хранящего все функции программы и расположенного в глобальном пространстве имен (namespace). Естественно такая реализация сулит множество проблем, которые могут быть решены с помощью самого действенного в программировании совета - разделяй и властвуй. В результате данный патерн предлагает реализовывать все вспомогательные службы (классы, объекты, функции) в виде независимых компонентов и хранить их (либо создавать в) локаторе служб - объекте, доступном глобально или базовому элементу системы.
Семантика паттерна может быть представлена схематически следующим образом:
Как видите, все очень просто. Паттерн реализуется через объект, способный хранить множество служб (объектов) в себе и предоставлять их по требованию (при вызове метода get
).
В качестве реальных примеров описания данного паттерна можно привести следующие:
- PSR-11 - один из FIG-стандартнов, описывающих семантику интерфейса локатора служб
- ContainerInterop - наиболее распространенная и поддерживаемая семантическая реализация этого паттерна
На первый взгляд все просто, не так ли? Осталось только обговорить некоторые детали и приступим к реализации:
- Скалярные данные
- Локатор служб может хранить не только объекты, а, к примеру, конфигурацию приложения в виде массива или любые скалярные данные.
- Фабрики
- Метод
get
локатора служб не обязует его предоставлять данный ему ранее на хранение объект (службу). На деле это более общий метод, который отвечает за предоставление службы по ее имени, даже если этой службы еще не существует (объект не создан). Делается это с помощью Фабрик. Фабрика это функция или целый класс, способный создавать объекты других классов и предоставлять их локатору служб по требованию. - Шаринг
- Если паттерн предполагает создание сервисов при их запросе, то как ограничить этот процесс, если это слишком затратная операция (как, на пример, соединение с базой данных)? Для этих целей локатор служб должен поддерживать Карту шаринга, которая разрешает или запрещает повторное создание сервиса при каждом его запросе.
- Псевдонимы
- Иногда один и тот же сервис необходимо предоставлять под разными именами. К примеру, одно имя “Машинное”, а другое “Человекочитаемое”. Для этих целей служат псевдонимы, по сути являющиеся ссылками на уже имеющиеся в локаторе служб сервисы.
- Иерархия
- Ничто не запрещает вложить один локатор служб в другой в виде сервиса. Такая организация позволит сформировать иерархию, которая, в свою очередь, поможет разделить ответственность за предоставление сервисов различных типов между несколькими хранилищами (еще одно название локатора служб).
Реализация
Простейшей реализацией данного паттерна служит следующий класс:
Использоваться такой класс может следующим образом:
Фабрики
Для понимания работы фабрик достаточно рассмотреть пример с сервисом доступа к базе данных. Предположим нам необходимо работать с базой данных через интерфейс PDO. Для этого необходимо предварительно зарегистрировать экземпляр данного класса в локаторе служб следующим образом:
Но это создает две проблемы:
- Зачем нам выполнять подключение к базе данных в момент добавления сервиса в локатор?
- Почему логика создания экземпляра класса
PDO
в ответственности локатора?
Обе эти проблемы решаются с использованием фабрик, которые представляют собой функции или объекты реализующие определенный интерфейс и создающие экземпляры сервисов при первой необходимости. Конечно для этого необходимо немного расширить класс ServiceLocator
:
Семантика классов-фабрик может быть описана следующим интерфейсом:
После эти небольших правок можно инкапсулировать логику создания экземпляра доступа к базе данных в фабрику:
Конечно в реальном проекте не следует регистрировать фабрики с помощью метода add
, так как это исключает возможность регистрировать фабрики как сервисы в локаторе служб, а это недопустимо.
Шаринг
Раз мы обсудили пример с регистрацией сервиса доступа к базе данных в локаторе, затроним вопрос шаринга этого сервиса. Для этого так же следует немного расширить класс ServiceLocator
:
После этих изменений служба доступа к базе данных будет создаваться только один раз при первом запросе:
Советы по использованию паттерна
Что нужно делать:
- Применяйте данный патерн для приведения в порядок вашей системы, ее инфраструктуры и зависимостей. Храните в локаторе служб все повторно используемые сервисы
- Используйте несколько локаторов служб, если это позволит более явно подчеркнуть различные компоненты системы. К примеру, все контроллеры вы можете хранить в одном локаторе, а сервисы уровня инфраструктуры в другом
- Храните сервисы в локаторе под именами их классов или реализуемых ими интерфейсов, если это возможно
Что не нужно делать:
- Не храните в локаторе элементы бизнес-модели
- Не предоставляйте всем элементам бизнес-модели прямой доступ к локатору служб, передавайте им из него только то, что им действительно нужно для работы