Посетитель на Go
Посетитель — это поведенческий паттерн, который позволяет добавить новую операцию для целой иерархии классов, не изменяя код этих классов.
Подробней о том, почему Посетитель нельзя заменить простой перегрузкой методов читайте в статье Посетитель и Double Dispatch.
Концептуальный пример
Паттерн Посетитель позволяет вам добавлять поведение в структуру без ее изменения. Представим, что вы разработчик библиотеки, которая содержит структуры разных фигур:
- Квадрат
- Круг
- Треугольник
Структуры каждой из вышеназванных фигур реализуют общий интерфейс фигуры.
Как только сотрудники в вашей компании начали использовать вашу замечательную библиотеку, вас засыпали просьбами добавить тот или иной функционал. Рассмотрим один из простейших вариантов: команда попросила вас добавить в структуру функцию getArea
, возвращающую площадь фигуры.
Существует много решений этой проблемы.
Первое, что приходит в голову – добавить метод getArea
напрямую в интерфейс фигуры, и затем реализовать его в каждой структуре. Это кажется самым правильным решением, но у него есть свои минусы. Как разработчик библиотеки, вы рискуете сломать ваш драгоценный код каждый раз, когда добавляете новое поведение. Несмотря на это, вы хотите дать другим командам возможность каким-то образом расширять вашу библиотеку.
Второй вариант – команда, которая запрашивает добавление новой функции, может сама реализовать ее в своем проекте. Однако, это не всегда является возможным, так как новый функционал может полагаться на скрытый код.
Третий возможный вариант — решить вышеуказанную проблему благодаря использованию паттерна Посетитель. Сперва мы определяем интерфейс посетителя следующим способом:
Функции visitForSquare(square)
, visitForCircle(circle)
, visitForTriangle(triangle)
позволят нам добавлять функционал для квадратов, кругов и треугольников соответственно.
Не понимаете, почему мы не можем оставить только один метод visit(shape)
в интерфейсе посетителя? Это невозможно из-за того, что язык Go не поддерживает перегрузку методов, поэтому вы не можете иметь методы с одинаковыми именами, но разными параметрами.
Второй, не менее важный, этап – добавление метода accept
в интерфейс фигуры.
Все структуры фигур должны определять этот метод похожим способом:
Погодите, разве я не заявлял чуть ранее, что не хочу менять существующие структуры фигур? К сожалению, во время использования паттерна Посетителя нам придется вносить изменения в структуры, но лишь единожды.
В случае добавления другого функционала, например getNumSides
или getMiddleCoordinates
, мы будем использовать все тот же метод accept(v visitor)
без новых изменений структур фигур.
В конечном итоге, структуры нужно изменить лишь единожды, и все будущие запросы нового функционала можно будет реализовать с помощью функции accept(v visitor)
. Если команда запросит поведение getArea
, мы можем просто определить явную реализацию интерфейса посетителя и прописать логику вычисления площади в этой конкретной имплементации.