注册模式(Registry)

目的

目的是能够存储在应用程序中经常使用的对象实例,通常会使用只有静态方法的抽象类来实现(或使用单例模式)。需要注意的是这里可能会引入全局的状态,我们需要使用依赖注入来避免它。

例子

  • Zend 框架 1:Zend_Registry 实现了整个应用程序的 logger 对象和前端控制器等
  • Yii 框架:CWebApplication 具有全部应用程序组件,例如 CWebUserCUrlManager 等。

UML 图

file

代码

你也可以在 GitHub 上找到此代码

Registry.php

  1. <?php
  2. namespace DesignPatterns\Structural\Registry;
  3. /**
  4. * 创建注册表抽象类。
  5. */
  6. abstract class Registry
  7. {
  8. const LOGGER = 'logger';
  9. /**
  10. * 这里将在你的应用中引入全局状态,但是不可以被模拟测试。
  11. * 因此被视作一种反抗模式!使用依赖注入进行替换!
  12. *
  13. * @var array
  14. * 定义存储值数组。
  15. */
  16. private static $storedValues = [];
  17. /**
  18. * @var array
  19. * 定义合法键名数组。
  20. * 可在此定义用户名唯一性。
  21. */
  22. private static $allowedKeys = [
  23. self::LOGGER,
  24. ];
  25. /**
  26. * @param string $key
  27. * @param mixed $value
  28. *
  29. * @return void
  30. * 设置键值,并保存进 $storedValues 。
  31. * 可视作设置密码。
  32. */
  33. public static function set(string $key, $value)
  34. {
  35. if (!in_array($key, self::$allowedKeys)) {
  36. throw new \InvalidArgumentException('Invalid key given');
  37. }
  38. self::$storedValues[$key] = $value;
  39. }
  40. /**
  41. * @param string $key
  42. *
  43. * @return mixed
  44. * 定义获取方法,获取已存储的对应键的值
  45. * 可视作验证用户环节,检查用户名是否存在,返回密码,后续验证密码正确性。
  46. */
  47. public static function get(string $key)
  48. {
  49. if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) {
  50. throw new \InvalidArgumentException('Invalid key given');
  51. }
  52. return self::$storedValues[$key];
  53. }
  54. }

测试

Tests/RegistryTest.php

  1. <?php
  2. namespace DesignPatterns\Structural\Registry\Tests;
  3. use DesignPatterns\Structural\Registry\Registry;
  4. use stdClass;
  5. use PHPUnit\Framework\TestCase;
  6. /**
  7. * 创建自动化测试单元。
  8. */
  9. class RegistryTest extends TestCase
  10. {
  11. public function testSetAndGetLogger()
  12. {
  13. $key = Registry::LOGGER;
  14. $logger = new stdClass();
  15. Registry::set($key, $logger);
  16. $storedLogger = Registry::get($key);
  17. $this->assertSame($logger, $storedLogger);
  18. $this->assertInstanceOf(stdClass::class, $storedLogger);
  19. }
  20. /**
  21. * @expectedException \InvalidArgumentException
  22. */
  23. public function testThrowsExceptionWhenTryingToSetInvalidKey()
  24. {
  25. Registry::set('foobar', new stdClass());
  26. }
  27. /**
  28. * 注 @在此运行隔离进程:没有它的话,前一个测试单元可能已经设置它,
  29. * 并且测试将不能运行,这就是为什么你应该实现依赖注入,
  30. * 因为注入类会很容易被测试单元替代。
  31. *
  32. * @runInSeparateProcess
  33. * @expectedException \InvalidArgumentException
  34. */
  35. public function testThrowsExceptionWhenTryingToGetNotSetKey()
  36. {
  37. Registry::get(Registry::LOGGER);
  38. }
  39. }