访问者模式(Visitor)

目的

访问者模式可以让你将对象操作外包给其他对象。这样做的最主要原因就是关注(数据结构和数据操作)分离。但是被访问的类必须定一个契约接受访问者。 (详见例子中的 Role::accept 方法)

契约可以是一个抽象类也可直接就是一个接口。在此情况下,每个访问者必须自行选择调用访问者的哪个方法。

UML 类图

file

代码

你可以在 GitHub 上找到这些代码

RoleVisitorInterface.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Visitor;
  3. /**
  4. * 注意:访问者不能自行选择调用哪个方法,
  5. * 它是由 Visitee 决定的。
  6. */
  7. interface RoleVisitorInterface
  8. {
  9. public function visitUser(User $role);
  10. public function visitGroup(Group $role);
  11. }

RoleVisitor.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Visitor;
  3. class RoleVisitor implements RoleVisitorInterface
  4. {
  5. /**
  6. * @var Role[]
  7. */
  8. private $visited = [];
  9. public function visitGroup(Group $role)
  10. {
  11. $this->visited[] = $role;
  12. }
  13. public function visitUser(User $role)
  14. {
  15. $this->visited[] = $role;
  16. }
  17. /**
  18. * @return Role[]
  19. */
  20. public function getVisited(): array
  21. {
  22. return $this->visited;
  23. }
  24. }

Role.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Visitor;
  3. interface Role
  4. {
  5. public function accept(RoleVisitorInterface $visitor);
  6. }

User.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Visitor;
  3. class User implements Role
  4. {
  5. /**
  6. * @var string
  7. */
  8. private $name;
  9. public function __construct(string $name)
  10. {
  11. $this->name = $name;
  12. }
  13. public function getName(): string
  14. {
  15. return sprintf('User %s', $this->name);
  16. }
  17. public function accept(RoleVisitorInterface $visitor)
  18. {
  19. $visitor->visitUser($this);
  20. }
  21. }

Group.php

  1. <?php
  2. namespace DesignPatterns\Behavioral\Visitor;
  3. class Group implements Role
  4. {
  5. /**
  6. * @var string
  7. */
  8. private $name;
  9. public function __construct(string $name)
  10. {
  11. $this->name = $name;
  12. }
  13. public function getName(): string
  14. {
  15. return sprintf('Group: %s', $this->name);
  16. }
  17. public function accept(RoleVisitorInterface $visitor)
  18. {
  19. $visitor->visitGroup($this);
  20. }
  21. }

测试

Tests/VisitorTest.php

  1. <?php
  2. namespace DesignPatterns\Tests\Visitor\Tests;
  3. use DesignPatterns\Behavioral\Visitor;
  4. use PHPUnit\Framework\TestCase;
  5. class VisitorTest extends TestCase
  6. {
  7. /**
  8. * @var Visitor\RoleVisitor
  9. */
  10. private $visitor;
  11. protected function setUp()
  12. {
  13. $this->visitor = new Visitor\RoleVisitor();
  14. }
  15. public function provideRoles()
  16. {
  17. return [
  18. [new Visitor\User('Dominik')],
  19. [new Visitor\Group('Administrators')],
  20. ];
  21. }
  22. /**
  23. * @dataProvider provideRoles
  24. *
  25. * @param Visitor\Role $role
  26. */
  27. public function testVisitSomeRole(Visitor\Role $role)
  28. {
  29. $role->accept($this->visitor);
  30. $this->assertSame($role, $this->visitor->getVisited()[0]);
  31. }
  32. }