职责链模式

目的

建立一个对象链,按顺序处理请求。如果一个对象不能处理请求,就将调用委托给职责链中的下一个对象,以此类推。

UML 类图

职责链模式类图

代码

Handler.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\Behavioral\ChainOfResponsibility;

use Psr\Http\Message\RequestInterface;

abstract class Handler
{
private ?Handler $successor = null;

public function __construct(Handler $handler = null)
{
$this->successor = $handler;
}

/**
* 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

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\Behavioral\ChainOfResponsibility\Responsible;

use DesignPatterns\Behavioral\ChainOfResponsibility\Handler;
use Psr\Http\Message\RequestInterface;

class HttpInMemoryCacheHandler extends Handler
{
private array $data;

public function __construct(array $data, ?Handler $successor = null)
{
parent::__construct($successor);

$this->data = $data;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\ChainOfResponsibility\Responsible;

use DesignPatterns\Behavioral\ChainOfResponsibility\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

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
<?php declare(strict_types = 1);

namespace DesignPatterns\Behavioral\ChainOfResponsibility\Tests;

use DesignPatterns\Behavioral\ChainOfResponsibility\Handler;
use DesignPatterns\Behavioral\ChainOfResponsibility\Responsible\HttpInMemoryCacheHandler;
use DesignPatterns\Behavioral\ChainOfResponsibility\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));
}
}