4.1. Локатор Служб (Service Locator)

Этот шаблон считается анти-паттерном!

Некоторые считают Локатор Служб анти-паттерном. Он нарушает принцип инверсии зависимостей (Dependency Inversion principle) из набора принципов SOLID. Локатор Служб скрывает зависимости данного класса вместо их совместного использования, как в случае шаблона Внедрение Зависимости (Dependency Injection). В случае изменения данных зависимостей мы рискуем сломать функционал классов, которые их используют, вследствие чего затрудняется поддержка системы.

4.1.1. Назначение

Для реализации слабосвязанной архитектуры, чтобы получить хорошо тестируемый, сопровождаемый и расширяемый код. Паттерн Инъекция зависимостей (DI) и паттерн Локатор Служб — это реализация паттерна Инверсия управления (Inversion of Control, IoC).

4.1.2. Использование

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

4.1.3. Диаграмма UML

Alt ServiceLocator UML Diagram

4.1.4. Код

Вы можете найти этот код на GitHub

Service.php

1<?php
2
3namespace DesignPatterns\More\ServiceLocator;
4
5interface Service
6{
7}

ServiceLocator.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\More\ServiceLocator;
 6
 7use OutOfRangeException;
 8use InvalidArgumentException;
 9
10class ServiceLocator
11{
12    /**
13     * @var string[][]
14     */
15    private array $services = [];
16
17    /**
18     * @var Service[]
19     */
20    private array $instantiated = [];
21
22    public function addInstance(string $class, Service $service)
23    {
24        $this->instantiated[$class] = $service;
25    }
26
27    public function addClass(string $class, array $params)
28    {
29        $this->services[$class] = $params;
30    }
31
32    public function has(string $interface): bool
33    {
34        return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
35    }
36
37    public function get(string $class): Service
38    {
39        if (isset($this->instantiated[$class])) {
40            return $this->instantiated[$class];
41        }
42
43        $object = new $class(...$this->services[$class]);
44
45        if (!$object instanceof Service) {
46            throw new InvalidArgumentException('Could not register service: is no instance of Service');
47        }
48
49        $this->instantiated[$class] = $object;
50
51        return $object;
52    }
53}

LogService.php

1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\More\ServiceLocator;
6
7class LogService implements Service
8{
9}

4.1.5. Тест

Tests/ServiceLocatorTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\More\ServiceLocator\Tests;
 6
 7use DesignPatterns\More\ServiceLocator\LogService;
 8use DesignPatterns\More\ServiceLocator\ServiceLocator;
 9use PHPUnit\Framework\TestCase;
10
11class ServiceLocatorTest extends TestCase
12{
13    private ServiceLocator $serviceLocator;
14
15    public function setUp(): void
16    {
17        $this->serviceLocator = new ServiceLocator();
18    }
19
20    public function testHasServices()
21    {
22        $this->serviceLocator->addInstance(LogService::class, new LogService());
23
24        $this->assertTrue($this->serviceLocator->has(LogService::class));
25        $this->assertFalse($this->serviceLocator->has(self::class));
26    }
27
28    public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet()
29    {
30        $this->serviceLocator->addClass(LogService::class, []);
31        $logger = $this->serviceLocator->get(LogService::class);
32
33        $this->assertInstanceOf(LogService::class, $logger);
34    }
35}