Шаблон "Посетитель (Visitor)" - mcodex

Шаблон «Посетитель (Visitor)»

Шаблон проектирования «Посетитель» относится к поведенческим шаблонам и позволяет классам реализовывать некий дополнительный, не свойственный им и не относящийся к их основной ответственности функционал, вынося его в отдельные классы-посетители.

Описание работы.

Предположим, в нашем проекте есть классы-клиенты, выполняющие определенные, специфические задачи. Появилась необходимость добавить им новый функционал, что бы они могли реализовать не свойственную им задачу. Самое простое — добавить соответствующие методы в каждый класс, однако это не всегда приемлемо, так как нарушает принципы 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);

Ссылки:

https://github.com/maximmas/patterns

https://refactoring.guru/ru/design-patterns

Оставьте комментарий