Шаблон проектирования «Посетитель» относится к поведенческим шаблонам и позволяет классам реализовывать некий дополнительный, не свойственный им и не относящийся к их основной ответственности функционал, вынося его в отдельные классы-посетители.
Описание работы.
Предположим, в нашем проекте есть классы-клиенты, выполняющие определенные, специфические задачи. Появилась необходимость добавить им новый функционал, что бы они могли реализовать не свойственную им задачу. Самое простое — добавить соответствующие методы в каждый класс, однако это не всегда приемлемо, так как нарушает принципы SRP, OCP и другие.
Вместо этого создается отдельный новый класс — посетитель, в котором есть реализующие требуемый функционал методы.
В классе-клиенте добавляется метод (часто имеющий название accept), в который принимает объект посетителя как параметр и вызывает у него соответствующий метод.
Таким образом, в классы-клиенты вносятся минимальные изменения (только принимающий метод), а весь дополнительный функционал реализуется в отдельном классе-посетителе.
Пример реализации
Есть CRM, в которой существуют разные типы записей — account, client, product, supplier и т.д. У каждого типа записи своя структура. Необходимо добавить реализацию экспорта всех записей в файл.
Для этого для каждого типа записи создается отдельный класс-посетитель, реализующий интерфейс Visitor с методом export.
В каждый класс записи добавляется метод accept, который принимает объект посетителя и вызывает его метод export.
В клиентском коде для экспорта у каждого экземпляра записи вызывается метод accept.
В результате всю работу по экспорту выполняет посетитель, а запись лишь предоставляет свои данные и вызывает метод посетителя, не реализуя самостоятельно логику экспорта.
В данном примере метод accept можно вынести в базовый класс записей, так как его реализация одинакова для всех. Однако в реальных проектах это подходит не всегда и метод accept требуется реализовывать каждом классе.
Интрефейсы
// интерфейс для записей
interface Entity
{
public function accept(Visitor $visitor): void;
}
// интерфейс для посетителей
interface Visitor
{
public function export(Entity $entity): void;
}Классы записей
class Product implements Entity
{
protected string $title;
protected float $price;
public function accept(Visitor $visitor): void
{
$visitor->export($this);
}
}
class Account implements Entity
{
protected int $id;
protected float $amount;
public function accept(Visitor $visitor): void
{
$visitor->export($this);
}
}Классы посетителей
class ProductVisitor implements Visitor
{
public function export(Entity $entity)
{
// логика экспорта
}
}
class ClientVisitor implements Visitor
{
public function export(Entity $entity)
{
// логика экспорта
}
}Клиентский код
$product = new Product;
$account = new Account;
// делаем экспорт
$account->accept(new AccountVisitor);
$product->accept(new ProductVisitor);