3.13. Visitor

3.13.1. Zweck

Das Visitor-Muster ermöglicht es, Operationen auf Objekten an andere Objekte zu übergeben. Der Hauptgrund dafür ist Separation Of Concerns. Klassen müssen dafür einen Vertrag definieren, um Besucher „herein zu bitten“ (in der ``Role::accept``Methode in diesem Beispiel).

Der Vertrag ist eine Abstrakte Klasse aber diese kann auch ein sauberes Interface besitzen. In diesem Fall entscheidet jeder Besucher selbst welche Methode er aufruft.

3.13.2. UML-Diagramm

Alt Visitor UML Diagram

3.13.3. Code

Du findest den Code hierzu auf 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. Test

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}