3.13. 访问者模式

3.13.1. 目的

访问者模式允许将对象上的操作外包给其他对象。这样做的主要原因是保持关注数据结构和数据操作的分离。但是类必须定义一个允许访问的契约(示例中的Role::accept方法)。

契约是一个抽象类,但你也可以就是一个接口。在这种情况下,每个Visitor必须自己选择要调用哪个方法。

3.13.2. UML 图

Alt Visitor UML Diagram

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}