服务定位器模式(Service Locator)

服务定位器模式被认为是一种反面模式!

服务定位器模式被一些人认为是一种反面模式。它违反了依赖倒置原则。该模式隐藏类的依赖,而不是暴露依赖(如果暴露可通过依赖注入的方式注入依赖)。当某项服务的依赖发生变化时,使用该服务的类的功能将面临被破坏的风险,最终导致系统难以维护。

目的

服务定位器模式能够降低代码的耦合度,以便获得可测试、可维护和可扩展的代码。DI 模式和服务定位器模式是 IOC 模式的一种实现。

用法

使用 ServiceLocator ,你可以为给定的 interface 注册一个服务。通过使用这个 interface,你不需要知道该服务的实现细节,就可以获取并在你应用中使用该服务。你可以在引导程序中配置和注入服务定位器对象。

例子

  • Zend Framework2 使用服务定位器创建和共享框架中使用的服务(EventManager,ModuleManager,以及由模块提供的用户自定义服务等)

UML 图例

file

代码

你也可以在 GitHub 上查看此代码

ServiceLocator.php

  1. <?php
  2. namespace DesignPatterns\More\ServiceLocator;
  3. class ServiceLocator
  4. {
  5. /**
  6. * @var array
  7. */
  8. private $services = [];
  9. /**
  10. * @var array
  11. */
  12. private $instantiated = [];
  13. /**
  14. * @var array
  15. */
  16. private $shared = [];
  17. /**
  18. * 相比在这里提供一个类,你也可以为接口存储一个服务。
  19. *
  20. * @param string $class
  21. * @param object $service
  22. * @param bool $share
  23. */
  24. public function addInstance(string $class, $service, bool $share = true)
  25. {
  26. $this->services[$class] = $service;
  27. $this->instantiated[$class] = $service;
  28. $this->shared[$class] = $share;
  29. }
  30. /**
  31. * 相比在这里提供一个类,你也可以为接口存储一个服务。
  32. *
  33. * @param string $class
  34. * @param array $params
  35. * @param bool $share
  36. */
  37. public function addClass(string $class, array $params, bool $share = true)
  38. {
  39. $this->services[$class] = $params;
  40. $this->shared[$class] = $share;
  41. }
  42. public function has(string $interface): bool
  43. {
  44. return isset($this->services[$interface]) || isset($this->instantiated[$interface]);
  45. }
  46. /**
  47. * @param string $class
  48. *
  49. * @return object
  50. */
  51. public function get(string $class)
  52. {
  53. if (isset($this->instantiated[$class]) && $this->shared[$class]) {
  54. return $this->instantiated[$class];
  55. }
  56. $args = $this->services[$class];
  57. switch (count($args)) {
  58. case 0:
  59. $object = new $class();
  60. break;
  61. case 1:
  62. $object = new $class($args[0]);
  63. break;
  64. case 2:
  65. $object = new $class($args[0], $args[1]);
  66. break;
  67. case 3:
  68. $object = new $class($args[0], $args[1], $args[2]);
  69. break;
  70. default:
  71. throw new \OutOfRangeException('Too many arguments given');
  72. }
  73. if ($this->shared[$class]) {
  74. $this->instantiated[$class] = $object;
  75. }
  76. return $object;
  77. }
  78. }

LogService.php

  1. <?php
  2. namespace DesignPatterns\More\ServiceLocator;
  3. class LogService
  4. {
  5. }

测试

Tests/ServiceLocatorTest.php

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