4.1. Lokalizator usług (Service Locator)

TEN WZORZEC PROJEKTOWY JEST UZNAWANY ZA ANTY-WZORZEC!

Lokalizator usług (ang. Service Locator) jest uważany przez część środowiska za anty-wzorzec. Łamie zasadę odwrócenia zależności ze zbioru zasad SOLID. Lokalizator usług ukrywa zależności danej klasy zamiast je udostępniać, jak ma to miejsce we wzorcu Wstrzykiwania zależności (ang. Dependency Injection). W przypadku zmiany w zależnościach ryzykujemy problemami z niepoprawnym działaniem klas, które z tych zależności korzystają. Powoduje to, że system staje się trudniejszy w utrzymaniu.

4.1.1. Przeznaczenie

Lokalizator usług jest używany do stworzenia luźno powiązanej architektury, w celu uzyskania kodu łatwiejszego w testowaniu, utrzymaniu i rozszerzaniu. Wzorce Wstrzykiwania zależności i Lokalizatora usług są implementacją wzorca Odwrócenia sterowania.

4.1.2. Użycie

Przy użyciu Lokalizatora usług możemy zarejestrować usługę pod daną nazwą interfejsu. Używając nazwy interfejsu możemy pobrać daną usługę i wykorzystywać ją dalej w klasach bez znajomości jej implementacji. Konfigurację i wstrzykiwanie obiektu Lokalizatora usług możemy ustawić na poziomie boostrapu aplikacji.

4.1.3. Diagram UML

Alt ServiceLocator UML Diagram

4.1.4. Kod

Ten kod znajdziesz również na 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. Testy

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}