PHP 责任链模式
目的
创建一个按顺序处理调用的对象链。如果一个对象不能处理调用,它将把调用委托给链中的下一个对象,以此类推。
例子
- 日志框架,其中每个链元素自主决定如何处理日志信息
- 垃圾邮件过滤器
- 缓存:第一个对象是一个例如Memcached的接口,如果“没有命中”它则委托调用数据库的接口
UML 图
代码
Handler.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\ChainOfResponsibilities; use Psr\Http\Message\RequestInterface; abstract class Handler { public function __construct(private ?Handler $successor = null) { } /** * This approach by using a template method pattern ensures you that * each subclass will not forget to call the successor */ final public function handle(RequestInterface $request): ?string { $processed = $this->processing($request); if ($processed === null && $this->successor !== null) { // the request has not been processed by this handler => see the next $processed = $this->successor->handle($request); } return $processed; } abstract protected function processing(RequestInterface $request): ?string; }
Responsible/FastStorage.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible; use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; use Psr\Http\Message\RequestInterface; class HttpInMemoryCacheHandler extends Handler { public function __construct(private array $data, ?Handler $successor = null) { parent::__construct($successor); } protected function processing(RequestInterface $request): ?string { $key = sprintf( '%s?%s', $request->getUri()->getPath(), $request->getUri()->getQuery() ); if ($request->getMethod() == 'GET' && isset($this->data[$key])) { return $this->data[$key]; } return null; } }
Responsible/SlowStorage.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible; use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; use Psr\Http\Message\RequestInterface; class SlowDatabaseHandler extends Handler { protected function processing(RequestInterface $request): ?string { // this is a mockup, in production code you would ask a slow (compared to in-memory) DB for the results return 'Hello World!'; } }
测试
Tests/ChainTest.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\ChainOfResponsibilities\Tests; use DesignPatterns\Behavioral\ChainOfResponsibilities\Handler; use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\HttpInMemoryCacheHandler; use DesignPatterns\Behavioral\ChainOfResponsibilities\Responsible\SlowDatabaseHandler; use PHPUnit\Framework\TestCase; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\UriInterface; class ChainTest extends TestCase { private Handler $chain; protected function setUp(): void { $this->chain = new HttpInMemoryCacheHandler( ['/foo/bar?index=1' => 'Hello In Memory!'], new SlowDatabaseHandler() ); } public function testCanRequestKeyInFastStorage() { $uri = $this->createMock(UriInterface::class); $uri->method('getPath')->willReturn('/foo/bar'); $uri->method('getQuery')->willReturn('index=1'); $request = $this->createMock(RequestInterface::class); $request->method('getMethod') ->willReturn('GET'); $request->method('getUri')->willReturn($uri); $this->assertSame('Hello In Memory!', $this->chain->handle($request)); } public function testCanRequestKeyInSlowStorage() { $uri = $this->createMock(UriInterface::class); $uri->method('getPath')->willReturn('/foo/baz'); $uri->method('getQuery')->willReturn(''); $request = $this->createMock(RequestInterface::class); $request->method('getMethod') ->willReturn('GET'); $request->method('getUri')->willReturn($uri); $this->assertSame('Hello World!', $this->chain->handle($request)); } }