3.13. Visitor

3.13.1. Amaç

Nesneler üzerindeki işlemleri, başka dış nesneler üzerine aktarmanıza olanak tanımak. Bunun yapılmasının başlıca nedeni, İlgilerin Ayrımı (Separation of Concerns) ilkesi uyarınca, sınıflar arasındaki iş bölümünün düzgün bir biçimde ilerlemesini sağlamaktır. Ancak sınıflar, ziyaretçilere izin vermek için bir sözleşme tanımlamak zorundadır (örn. aşağıdaki örnek kodda görebileceğiniz Role::accept() yöntemi).

Sözleşme soyut bir sınıftır, ancak bu sınıf yerine temiz bir arayüze de sahip olabilirsiniz. Bu durumda her Ziyaretçi, ziyaretçi üzerinde hangi yöntemi çağıracağını kendisi seçmelidir.

3.13.2. UML Diyagramı

Alt Visitor UML Diyagramı

3.13.3. Kod

Bu kodu Github üzerinde de bulabilirsiniz.

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}