Простое для понимания руководство, которое поможет вам понять структуру посетителей и двойную отправку с конкретными примерами.

Вам будут представлены четкие объяснения обеих тем, а также интуитивно понятные примеры и фрагменты кода. Как всегда, я включил скриншоты из IDE, а также сделал общедоступный репозиторий git, где вы можете получить доступ ко всему коду, указанному в статье. Код также включен в статью как Github Gists.

Обзор статьи

  • Введение
  • Объяснение двойной отправки
  • Объяснение шаблона посетителя
  • Важное примечание — каковы хорошие варианты использования шаблона посетителя?
  • Резюме

Объяснение двойной отправки

Двойная диспетчеризация — это языковая функция, позволяющая динамически вызывать различные функции в зависимости от типа времени выполнения двух объектов. И объект, из которого вызывается метод, и объект, переданный методу в качестве аргумента. В качестве примера представьте, что у вас есть несколько персонажей фильма, которые могут взаимодействовать друг с другом, и вы хотите реализовать разные результаты в зависимости от того, какие два персонажа взаимодействуют. Предположим, что персонажи Супермен и Бэтмен взаимодействуют друг с другом, это может привести к тому, что они скажут Привет!. В качестве альтернативы, если Джокер и Бэтмен взаимодействуют, это может привести к Почему так серьезно?. Дело в том, что исход взаимодействия зависит от обоих персонажей (тип обоих персонажей). Давайте посмотрим на некоторый код Java, чтобы проиллюстрировать это, вы также можете найти весь код в этом репозитории.

Во-первых, у нас есть интерфейс персонажа:

Этот интерфейс указывает, что для каждого типа столкновения будет существовать метод interact.

Далее у нас есть три разных персонажа и то, как они реализуют методы взаимодействия. В коде используется перегрузка методов, и идея состоит в том, что тип входного параметра, передаваемого в interact, определяет, какой метод перегрузки будет вызван.

Джокер

Бэтмен

Супермен

Наконец, идет метод main, в котором мы создаем экземпляры классов и пробуем некоторые взаимодействия.

Создание экземпляров и взаимодействие между классами

Код создает 3 объекта, по одному для каждого типа персонажа фильма. После этого joker взаимодействует с batman, а superman взаимодействует с batman. Судя по комментариям в строках 7 и 8, мы ожидаем результатов: "Почему так серьезно?" и "Супермен передает привет Бэтмену". Однако вывод выглядит следующим образом (скриншот из моего терминала IDE):

Это выходные данные перегрузки метода, которая принимает тип Character в качестве входных данных, найденный в строке 6 как в Joker.java, так и в Superman.java.

Почему это происходит?

Это происходит из-за того, что Java не поддерживает двойную отправку. Поэтому невозможно во время выполнения определить тип класса объекта, переданного в качестве входных данных. Java допускает однократную отправку, поэтому он может определить, что объекты joker и superman относятся к классам Joker и Superman, даже если переменные были созданы с классом Character. Однако для достижения желаемого поведения потребуется, чтобы язык включал двойную диспетчеризацию, чего нет в Java. Но что нам тогда делать, если мы хотим такого поведения двойной отправки?

Шаблон посетителя позволит нам решить эту проблему!

Объяснение шаблона посетителя

Прежде чем рассматривать пример шаблона посетителя, я хочу, чтобы вы обратили внимание на следующее: первый пример, где персонажи фильма взаимодействуют друг с другом, является хорошим примером для демонстрации того, что такое двойная диспетчеризация и что происходит, когда двойная диспетчеризация не поддерживается в язык программирования. Однако этот пример не является хорошим примером использования шаблона посетителя. Вместо этого мы немного изменим ситуацию в примере, чтобы лучше показать сильные и слабые стороны шаблона посетителя.

Предыдущий пример: у нас были персонажи фильма, которые взаимодействовали друг с другом. У каждого персонажа был метод взаимодействия с любым другим супергероем или злодеем. Была только одна категория типов, а именно символы, обозначенные интерфейсом Character.

Пример шаблона посетителя: мы по-прежнему имеем дело с персонажами фильмов, однако теперь мы различаем два типа: супергерои и злодеи. Супергероев будут посещать злодеи, и в зависимости от того, какой злодей посетит какого супергероя, произойдет определенный результат. Логика взаимодействия также будет удалена из классов супергероев и будет существовать только для классов злодеев (посетителей). Каждый злодей будет представлен отдельным посетителем. Давайте посмотрим на некоторый код:

Классы супергероев

В приведенном выше коде показан интерфейс Superhero и три класса, которые его реализуют. Классы очень простые и содержат только метод с именем accept. Этот метод является неотъемлемой частью шаблона посетителя. Он принимает VillainVisitor в качестве входных данных и вызывает метод посетителя visit, предоставляя this в качестве входных данных. this в данном случае относится к классу, вызвавшему метод accept . Итак, если класс Batman вызывает accept, то this относится к классу Batman. Таким образом, посетитель знает, какой тип класса он посетит. Далее давайте посмотрим на посетителей:

Посетители

Выше мы видим интерфейс VillainVisitor и двух посетителей, которые его реализуют, LokiVisitor и JokerVisitor. Помните, я ранее упоминал, что логика взаимодействия была перенесена из классов супергероев в классы злодеев? Здесь это становится очевидным. Оба посетителя имеют методы посещения каждого героя. Обратите внимание, что различные методы visit принимают экземпляр соответствующего супергероя в качестве входного параметра, именно здесь вводится ключевое слово this из предыдущего. Наконец, давайте посмотрим, работает ли шаблон посетителя и позволяет ли нам динамически вызывать функции в зависимости как от вызывающего, так и от вызываемого:

Экземпляр в main

Мы начнем с создания экземпляра каждого из Superhero и одного из каждого VillainVisitor. Затем мы используем метод accept от супергероев, чтобы принять посетителей. Помните, что accept вызывает метод visit посетителя. Давайте посмотрим на вывод и посмотрим, соответствует ли он желаемому.

Вывод

Успех!Выход соответствует нашим ожиданиям (см. встроенные комментарии в Example.java).

Шаблон посетителя имитировал эффект двойной отправки. Это позволило определить во время выполнения как тип вызывающего объекта, так и тип объекта, переданного в качестве параметра, и это приводит к правильному вызову методов.

Важное примечание. Каковы хорошие варианты использования шаблона посетителя?

В эту статью включены два разных примера. Один, чтобы объяснить двойную отправку, а другой, чтобы объяснить шаблон посетителей. Причина, по которой я использовал разные примеры, заключается в том, что их варианты использования различны. Первый пример вращался вокруг нескольких классов, которые взаимодействовали друг с другом. Второй пример вместо этого включал классы героев и посетителей-злодеев, что подразумевает разницу между двумя категориями. Это хороший пример использования шаблона посетителя. Как правило, шаблон посетителей хорош, когда у вас есть набор классов, которые редко растут (в данном случае супергерои). С другой стороны, набор посетителей можно часто изменять без особых усилий, а также легко добавлять новых посетителей.
Например, если нужно добавить нового злодея, вам потребуется только реализовать класс посетителя этого злодея и реализовать интерфейс VillainVisitor. Кроме того, если требуется посетитель совершенно нового типа, скажем, SidekickVisitor, тогда вам просто нужно добавить дополнительный метод accept в каждый класс супергероев, который принимает SidekickVisitor в качестве входных данных. В классе Batman это могло бы выглядеть так:

Batman.java с методами принятия для двух разных типов посетителей

Настоящим примером использования шаблона посетителя является обход древовидной структуры узлов, где необходимо посетить каждый узел. Это может быть при реализации компилятора. для языка программирования. Компилятор состоит из разных частей, две из которых — проверка типов и генерация кода. Такой вариант использования требует шаблона посетителя, так как вы можете реализовать TypeCheckerVisitor и CodeGenerationVisitor , каждый из которых отвечает за выполнение конкретных типов задач в зависимости от тип узла, который они посещают в древовидной структуре.

Резюме

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

Если у вас есть какие-либо вопросы, комментарии или советы по улучшению статьи, пожалуйста, не стесняйтесь обращаться ко мне!

Большое спасибо, что нашли время, чтобы прочитать этот пост, я надеюсь, что вы узнали что-то полезное!

Продолжайте учиться!
— Джейкоб Тофтгаард Расмуссен