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}