В отрасли существует устойчивый консенсус в отношении того, что Поиск сервисов является антипаттерном.
Из вики:
Стоит отметить, что в некоторых случаях локатор сервисов на самом деле является антишаблоном.В этом посте я рассматриваю случай, когда, по моему мнению, Service Locator не является антипаттерном.
Вот что пишут в Интернете Локатор :
Некоторые считают Service Locator антишаблоном.Service Locator настолько тесно связан с DI, что некоторые авторы (Марк Зееманн, Стивен ван Дёрсен) специально предупреждать :Это нарушает принцип инверсии зависимостей ( Принцип инверсии зависимостей ) из набора принципов ТВЕРДЫЙ .
Локатор служб скрывает зависимости данного класса вместо того, чтобы делиться ими, как в случае с шаблоном внедрения зависимостей ( Внедрение зависимости ).
Если эти зависимости изменятся, мы рискуем нарушить функциональность классов, которые их используют, что затрудняет поддержку системы.
Service Locator — опасный шаблон, поскольку он почти работает. .То есть Локатор чертовски хорош и работает почти как надо, но есть один момент, который все портит. А вот и он:Есть только одна область, где Service Locator не справляется, и к этому не следует относиться легкомысленно.
Основная проблема с Поиск сервисов это влияние возможности повторного использования классов, использующих его.То есть шаблон снижает возможность повторного использования кода определенного класса по двум причинам: во-первых, из-за ненужной зависимости класса от самого Локатора, во-вторых, становится непонятно, какие зависимости использует класс.Это проявляется двумя способами: * Класс тянется по Поиск сервисов как излишний Зависимость .
* Класс делает неочевидным, что это такое Зависимости являются.
Другими словами, именно так благословляется создание объектов и внедрение в них зависимостей:
а вот так - нет:public function __construct(IDep1 $dep1, IDep2 $dep2, IDep3 $dep3) { $this->dep1 = $dep1; $this->dep2 = $dep2; $this->dep3 = $dep3; }
public function __construct(ILocator $locator)
{
$this->locator = $locator;
$this->dep1 = $locator->get(IDep1::class);
$this->dep2 = $locator->get(IDep2::class);
$this->dep3 = $locator->get(IDep3::class);
}
В то же время внедрение зависимостей через свойства (аксессоры) тех же авторов в ряде случаев признается кошерным (например, когда внедряемые зависимости являются необязательными или установлены по умолчанию в конструкторе и могут быть переопределены в дальнейшем):
Внедрение свойств следует использовать только в том случае, если разрабатываемый вами класс имеет хорошее локальное значение по умолчанию, и вы все равно хотите, чтобы вызывающие объекты предоставляли различные реализации зависимости класса.Если мы перепишем наш класс Locator следующим образом:Важно отметить, что внедрение свойств лучше всего использовать, когда зависимость не является обязательной.
Если требуется зависимость, всегда лучше использовать внедрение в конструктор.
public function __construct(ILocator $locator = null)
{
if ($locator) {
$this->dep1 = $locator->get(IDep1::class);
}
}
public function setDep1(IDep1 $dep1)
{
$this->dep1 = $dep1;
}
затем: а) делаем его независимым от наличия Локатора (например, в тестовой среде), б) явно выделяем зависимости в сеттерах (можно также аннотировать, документировать, префиксить и решать проблему «неочевидности» зависимостей любым другим доступным способом, вплоть до Ctrl+F на клавише " $locator-> получить "в коде).
Теперь мы подошли к тому моменту, когда, на мой взгляд, использование Latitude оправдано.
В комментариях к статье " В чем основная разница между внедрением зависимостей и локатором сервисов? " коллега @symbix резюмировал тему статьи следующим образом:
SL работает по принципу извлечения: конструктор «вытягивает» свои зависимости из контейнера.То есть, по сути, контейнер DI-объектов можно использовать еще и как Service Locator:DI работает по принципу push: контейнер передает свои зависимости конструктору.
// push deps into constructor
public function __construct(IDep1 $dep1, IDep2 $dep2, IDep3 $dep3) {}
// pull deps from constructor
public function __construct(IContainer $container) {
if ($container) {
$this->dep1 = $container->get(IDep1::class);
$this->dep2 = $container->get(IDep2::class);
$this->dep3 = $container->get(IDep3::class);
}
}
Как мы отметили выше, первый способ считается приемлемым, второй — антипаттерном.
Но к чему приводит использование первого метода в промышленных масштабах? Более того, чтобы запустить приложение, при его создании мы должны внедрить в конструктор приложения его зависимости, а в их конструкторы — зависимости зависимостей и т. д. по всей иерархии.
То есть для того, чтобы объект приложения хотя бы был создан, мы должны создать все зависимости, упомянутые во всех конструкторах в цепочке, даже если мы просто собираемся получить справку о параметрах запуска приложения в консольном режиме.
«Антишаблон» Service Locator позволяет нам «вытягивать» нужные нам зависимости из контейнера при доступе к ним: class App {
/** @var \IContainer */
private $container;
/** @var \IDep1 */
private $dep1;
public function __construct(IContainer $container = null) {
$this->container = $container;
}
private function initDep1() {
if (!$this->dep1) {
$this->dep1 = $this->container->get(IDep1::class);
}
return $this->dep1;
}
public function run() {
$dep1 = $this->initDep1();
}
public function setDep1(IDep1 $dep1) {
$this->dep1 = $dep1;
}
}
Итак, приведенный выше код:
- можно использовать без контейнера в конструкторе благодаря возможности внедрения зависимостей через сеттер (например, в тестах);
- зависимости явно описываются через набор частных методов с префиксом
init
; - иерархия зависимостей не расширяется при создании экземпляра данного класса, а создается по мере его использования.
Ну, за небольшим исключением — при внедрении зависимостей в конструктор (режим push) DI-контейнер знает, для какого класса создаются зависимости, и может реализовать разные реализации одного и того же интерфейса на основе внутренних инструкций.
В режиме «pull» у контейнера нет информации, для кого он создает зависимости, ее нужно предоставить: $this->dep1 = $this->container->get(IDep1::class, self::class);
В этой версии Service Locator становится «шаблоном» без каких-либо «анти».
Послесловие
В комментариях к публикации благодарность коллегам @lair И @Максклуб пришли к выводу, что проблема отложенного внедрения зависимостей при создании объектов решается в рамках парадигмы DI, если соответствующий язык программирования поддерживает дженерики или проксирование.В случае PHP, в котором нет дженериков, требуется дополнительная генерация кода (автоматически — github.com/Ocramius/ProxyManager или вручную).
Таким образом, предлагаемое решение (реализация DI-контейнера в качестве Service Locator) все еще имеет свою нишу — проекты на языках без дженериков и проксирования, в которых дополнительная генерация кода нежелательна.
Но в подавляющем большинстве случаев лучше использовать «чистый» DI. Теги: #программирование #php #ИТ-стандарты #Идеальный код #шаблоны #шаблоны #антипаттерны #антипаттерны #мысли вслух #локатор сервисов
-
Сиджвик, Генри
19 Oct, 24 -
Рамблер: Они Тебя Ищут?
19 Oct, 24 -
Коммуникатор Qigi I6
19 Oct, 24 -
Компьютерная Преступность: Тогда И Сейчас
19 Oct, 24 -
Вам Нравятся Автопутешествия?
19 Oct, 24