责任链模式(Chain Of Responsibilities)

目的

建立一个对象链来按指定顺序处理调用。如果其中一个对象无法处理命令,它会委托这个调用给它的下一个对象来进行处理,以此类推。

例子

  • 日志框架,每个链元素自主决定如何处理日志消息。
  • 垃圾邮件过滤器。
  • 缓存:例如第一个对象是一个 Memcached 接口实例,如果 “丢失” 它会委托数据库接口处理这个调用。
  • Yii 框架: CFilterChain 是一个控制器行为过滤器链。执行点会有链上的过滤器逐个传递,并且只有当所有的过滤器验证通过,这个行为最后才会被调用。

UML 图

file

代码

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

Handler.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\ChainOfResponsibilities;
  3. use Psr\Http\Message\RequestInterface;
  4. use Psr\Http\Message\ResponseInterface;
  5. /**
  6. * 创建处理器抽象类 Handler 。
  7. */
  8. abstract class Handler
  9. {
  10. /**
  11. * @var Handler|null
  12. * 定义继承处理器
  13. */
  14. private $successor = null;
  15. /**
  16. * 输入集成处理器对象。
  17. */
  18. public function __construct(Handler $handler = null)
  19. {
  20. $this->successor = $handler;
  21. }
  22. /**
  23. * 通过使用模板方法模式这种方法可以确保每个子类不会忽略调用继
  24. * 承。
  25. *
  26. * @param RequestInterface $request
  27. * 定义处理请求方法。
  28. *
  29. * @return string|null
  30. */
  31. final public function handle(RequestInterface $request)
  32. {
  33. $processed = $this->processing($request);
  34. if ($processed === null) {
  35. // 请求尚未被目前的处理器处理 => 传递到下一个处理器。
  36. if ($this->successor !== null) {
  37. $processed = $this->successor->handle($request);
  38. }
  39. }
  40. return $processed;
  41. }
  42. /**
  43. * 声明处理方法。
  44. */
  45. abstract protected function processing(RequestInterface $request);
  46. }

Responsible/FastStorage.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
  3. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  4. use Psr\Http\Message\RequestInterface;
  5. /**
  6. * 创建 http 缓存处理类。
  7. */
  8. class HttpInMemoryCacheHandler extends Handler
  9. {
  10. /**
  11. * @var array
  12. */
  13. private $data;
  14. /**
  15. * @param array $data
  16. * 传入数据数组参数。
  17. * @param Handler|null $successor
  18. * 传入处理器类对象 $successor 。
  19. */
  20. public function __construct(array $data, Handler $successor = null)
  21. {
  22. parent::__construct($successor);
  23. $this->data = $data;
  24. }
  25. /**
  26. * @param RequestInterface $request
  27. * 传入请求类对象参数 $request 。
  28. * @return string|null
  29. *
  30. * 返回缓存中对应路径存储的数据。
  31. */
  32. protected function processing(RequestInterface $request)
  33. {
  34. $key = sprintf(
  35. '%s?%s',
  36. $request->getUri()->getPath(),
  37. $request->getUri()->getQuery()
  38. );
  39. if ($request->getMethod() == 'GET' && isset($this->data[$key])) {
  40. return $this->data[$key];
  41. }
  42. return null;
  43. }
  44. }

Responsible/SlowStorage.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible;
  3. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  4. use Psr\Http\Message\RequestInterface;
  5. /**
  6. * 创建数据库处理器。
  7. */
  8. class SlowDatabaseHandler extends Handler
  9. {
  10. /**
  11. * @param RequestInterface $request
  12. * 传入请求类对象 $request 。
  13. *
  14. * @return string|null
  15. * 定义处理方法,下面应该是个数据库查询动作,但是简单化模拟,直接返回一个 'Hello World' 字符串作查询结果。
  16. */
  17. protected function processing(RequestInterface $request)
  18. {
  19. // 这是一个模拟输出, 在生产代码中你应该调用一个缓慢的 (相对于内存来说) 数据库查询结果。
  20. return 'Hello World!';
  21. }
  22. }

测试

Tests/ChainTest.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests;
  3. use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler;
  4. use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler;
  5. use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler;
  6. use PHPUnit\Framework\TestCase;
  7. /**
  8. * 创建一个自动化测试单元 ChainTest 。
  9. */
  10. class ChainTest extends TestCase
  11. {
  12. /**
  13. * @var Handler
  14. */
  15. private $chain;
  16. /**
  17. * 模拟设置缓存处理器的缓存数据。
  18. */
  19. protected function setUp()
  20. {
  21. $this->chain = new HttpInMemoryCacheHandler(
  22. ['/foo/bar?index=1' => 'Hello In Memory!'],
  23. new SlowDatabaseHandler()
  24. );
  25. }
  26. /**
  27. * 模拟从缓存中拉取数据。
  28. */
  29. public function testCanRequestKeyInFastStorage()
  30. {
  31. $uri = $this->createMock('Psr\Http\Message\UriInterface');
  32. $uri->method('getPath')->willReturn('/foo/bar');
  33. $uri->method('getQuery')->willReturn('index=1');
  34. $request = $this->createMock('Psr\Http\Message\RequestInterface');
  35. $request->method('getMethod')
  36. ->willReturn('GET');
  37. $request->method('getUri')->willReturn($uri);
  38. $this->assertEquals('Hello In Memory!', $this->chain->handle($request));
  39. }
  40. /**
  41. * 模拟从数据库中拉取数据。
  42. */
  43. public function testCanRequestKeyInSlowStorage()
  44. {
  45. $uri = $this->createMock('Psr\Http\Message\UriInterface');
  46. $uri->method('getPath')->willReturn('/foo/baz');
  47. $uri->method('getQuery')->willReturn('');
  48. $request = $this->createMock('Psr\Http\Message\RequestInterface');
  49. $request->method('getMethod')
  50. ->willReturn('GET');
  51. $request->method('getUri')->willReturn($uri);
  52. $this->assertEquals('Hello World!', $this->chain->handle($request));
  53. }
  54. }