服务定位器模式

注:该模式被视为一种反模式。
一些人认为服务定位器模式是一种反模式,因为它违反了依赖倒置原则。服务定位器隐藏了类的依赖关系,而不是像使用依赖注入那样暴露它们。如果这些依赖关系发生了变化,你有可能破坏使用这些依赖的类的功能,导致系统变得难以维护。

目的

为了获得更好的可测试、可维护和可扩展的代码,服务定位器模式实现了松散耦合的架构。依赖注入模式和服务定位器模式是控制模式的一种实现。

用法

通过 ServiceLocator,你可以为一个给定的接口注册一个服务。利用该接口,你可以在不知道具体实现的情况下,检索接口服务并在应用程序中使用它。你可以在加载时配置和注入 ServiceLocator 对象。

UML 类图

服务定位器模式类图

代码

Service.php

1
2
3
4
5
6
7
8
<?php

namespace DesignPatterns\Extend\ServiceLocator;

interface Service
{

}

ServiceLocator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\ServiceLocator;

use OutOfRangeException;
use InvalidArgumentException;

class ServiceLocator
{
/**
* @var string[][]
*/
private array $services = [];

/**
* @var Service[]
*/
private array $instantiated = [];

public function addInstance(string $class, Service $service)
{
$this->instantiated[$class] = $service;
}

public function addClass(string $class, array $params)
{
$this->services[$class] = $params;
}

public function has(string $interface): bool
{
return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
}

public function get(string $class): Service
{
if (isset($this->instantiated[$class])) {
return $this->instantiated[$class];
}

$args = $this->services[$class];

switch (count($args)) {
case 0:
$object = new $class();
break;
case 1:
$object = new $class($args[0]);
break;
case 2:
$object = new $class($args[0], $args[1]);
break;
case 3:
$object = new $class($args[0], $args[1], $args[2]);
break;
default:
throw new OutOfRangeException('Too many arguments given');
}

if (!$object instanceof Service) {
throw new InvalidArgumentException('Could not register service: is no instance of Service');
}

$this->instantiated[$class] = $object;

return $object;
}
}

LogService.php

1
2
3
4
5
6
7
8
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\ServiceLocator;

class LogService implements Service
{

}

测试

Tests/ServiceLocatorTest.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php declare(strict_types = 1);

namespace DesignPatterns\Extend\ServiceLocator\Tests;

use DesignPatterns\Extend\ServiceLocator\LogService;
use DesignPatterns\Extend\ServiceLocator\ServiceLocator;
use PHPUnit\Framework\TestCase;

class ServiceLocatorTest extends TestCase
{
private ServiceLocator $serviceLocator;

public function setUp(): void
{
$this->serviceLocator = new ServiceLocator();
}

public function testHasServices()
{
$this->serviceLocator->addInstance(LogService::class, new LogService());

$this->assertTrue($this->serviceLocator->has(LogService::class));
$this->assertFalse($this->serviceLocator->has(self::class));
}

public function testGetWillInstantiateLogServiceIfNoInstanceHasBeenCreatedYet()
{
$this->serviceLocator->addClass(LogService::class, []);
$logger = $this->serviceLocator->get(LogService::class);

$this->assertInstanceOf(LogService::class, $logger);
}
}