3.13. 访问者模式
3.13.1. 目的
访问者模式允许将对象上的操作外包给其他对象。这样做的主要原因是保持关注数据结构和数据操作的分离。但是类必须定义一个允许访问的契约(示例中的Role::accept方法)。
契约是一个抽象类,但你也可以就是一个接口。在这种情况下,每个Visitor必须自己选择要调用哪个方法。
3.13.2. UML 图

3.13.3. 代码
在 GitHub 上查看代码
RoleVisitor.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7/**
8 * Note: the visitor must not choose itself which method to
9 * invoke, it is the visited object that makes this decision
10 */
11interface RoleVisitor
12{
13 public function visitUser(User $role);
14
15 public function visitGroup(Group $role);
16}
RecordingVisitor.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class RecordingVisitor implements RoleVisitor
8{
9 /**
10 * @var Role[]
11 */
12 private array $visited = [];
13
14 public function visitGroup(Group $role)
15 {
16 $this->visited[] = $role;
17 }
18
19 public function visitUser(User $role)
20 {
21 $this->visited[] = $role;
22 }
23
24 /**
25 * @return Role[]
26 */
27 public function getVisited(): array
28 {
29 return $this->visited;
30 }
31}
Role.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7interface Role
8{
9 public function accept(RoleVisitor $visitor);
10}
User.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class User implements Role
8{
9 public function __construct(private string $name)
10 {
11 }
12
13 public function getName(): string
14 {
15 return sprintf('User %s', $this->name);
16 }
17
18 public function accept(RoleVisitor $visitor)
19 {
20 $visitor->visitUser($this);
21 }
22}
Group.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Behavioral\Visitor;
6
7class Group implements Role
8{
9 public function __construct(private string $name)
10 {
11 }
12
13 public function getName(): string
14 {
15 return sprintf('Group: %s', $this->name);
16 }
17
18 public function accept(RoleVisitor $visitor)
19 {
20 $visitor->visitGroup($this);
21 }
22}
3.13.4. 测试
Tests/VisitorTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace DesignPatterns\Tests\Visitor\Tests;
6
7use DesignPatterns\Behavioral\Visitor\RecordingVisitor;
8use DesignPatterns\Behavioral\Visitor\User;
9use DesignPatterns\Behavioral\Visitor\Group;
10use DesignPatterns\Behavioral\Visitor\Role;
11use DesignPatterns\Behavioral\Visitor;
12use PHPUnit\Framework\TestCase;
13
14class VisitorTest extends TestCase
15{
16 private RecordingVisitor $visitor;
17
18 protected function setUp(): void
19 {
20 $this->visitor = new RecordingVisitor();
21 }
22
23 public function provideRoles()
24 {
25 return [
26 [new User('Dominik')],
27 [new Group('Administrators')],
28 ];
29 }
30
31 /**
32 * @dataProvider provideRoles
33 */
34 public function testVisitSomeRole(Role $role)
35 {
36 $role->accept($this->visitor);
37 $this->assertSame($role, $this->visitor->getVisited()[0]);
38 }
39}