Development Blog
Collection of development articles I've put together...
Visitor Pattern
The Visitor Pattern lets you outsource operations on objects to other objects. The main reason to do this is to keep a separation of concerns. But classes have to define a contract to allow visitors.
0
Likes
Stuart Todd
•
2 years ago
•
Design Patterns: Behavioural
Purpose
The purpose of the design pattern
The Visitor Pattern lets you outsource operations on objects to other objects. The main reason to do this is to keep a separation of concerns. But classes have to define a contract to allow visitors (the Role::accept method in the example).
The contract is an abstract class but you can have also a clean interface. In that case, each Visitor has to choose itself which method to invoke on the visitor.
An approach to add objects (known as roles) to other objects (known as visitors). The visitor object visits a role object and in doing so, the role becomes attached to the visitor (allowing it to access its methods and properties).
UML
UML design pattern diagram
Code
Code snippets
Role
Role interface which defines an accept method which is passed an instance of RoleVisitor.
namespace DesignPatterns\Behavioral\Visitor;
interface Role
{
public function accept(RoleVisitor $visitor);
}
Group
Group implements Role interface. The accept method expects an instance of RoleVisitor, once accept is called, the visitor attaches the role class to itself (via the visitGroup method).
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);
}
}
User
User implements Role interface. The accept method expects an instance of RoleVisitor, once accept is called, the visitor attaches the role class to itself (via the VisitUser method).
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);
}
}
RoleVisitor
RoleVisitor interface that the visitor subclasses must implement.
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
The RecordingVisitor is the visitor class which ‘visits' role classes and in doing the visitor class obtains access to the role class; its methods and properties.
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;
}
}
Tests
Unit test demonstrating how it works.
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]);
}