PHP 访问者模式
目的
访问者模式允许将对象上的操作外包给其他对象。这样做的主要原因是保持关注数据结构和数据操作的分离。但是类必须定义一个允许访问的契约(示例中的Role::accept方法)。
契约是一个抽象类,但你也可以就是一个接口。在这种情况下,每个Visitor必须自己选择要调用哪个方法。
UML 图
代码
RoleVisitor.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Visitor; /** * Note: the visitor must not choose itself which method to * invoke, it is the visited object that makes this decision */ interface RoleVisitor { public function visitUser(User $role); public function visitGroup(Group $role); }
RecordingVisitor.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Visitor; class RecordingVisitor implements RoleVisitor { /** * @var Role[] */ private array $visited = []; public function visitGroup(Group $role) { $this->visited[] = $role; } public function visitUser(User $role) { $this->visited[] = $role; } /** * @return Role[] */ public function getVisited(): array { return $this->visited; } }
Role.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Visitor; interface Role { public function accept(RoleVisitor $visitor); }
User.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Visitor; class User implements Role { public function __construct(private string $name) { } public function getName(): string { return sprintf('User %s', $this->name); } public function accept(RoleVisitor $visitor) { $visitor->visitUser($this); } }
Group.php
<?php declare(strict_types=1); namespace DesignPatterns\Behavioral\Visitor; class Group implements Role { public function __construct(private string $name) { } public function getName(): string { return sprintf('Group: %s', $this->name); } public function accept(RoleVisitor $visitor) { $visitor->visitGroup($this); } }
测试
Tests/VisitorTest.php
<?php declare(strict_types=1); namespace DesignPatterns\Tests\Visitor\Tests; use DesignPatterns\Behavioral\Visitor\RecordingVisitor; use DesignPatterns\Behavioral\Visitor\User; use DesignPatterns\Behavioral\Visitor\Group; use DesignPatterns\Behavioral\Visitor\Role; use DesignPatterns\Behavioral\Visitor; use PHPUnit\Framework\TestCase; class VisitorTest extends TestCase { private RecordingVisitor $visitor; protected function setUp(): void { $this->visitor = new RecordingVisitor(); } public function provideRoles() { return [ [new User('Dominik')], [new Group('Administrators')], ]; } /** * @dataProvider provideRoles */ public function testVisitSomeRole(Role $role) { $role->accept($this->visitor); $this->assertSame($role, $this->visitor->getVisited()[0]); } }