В современном языке Java паттерн Посетитель (Visitor) уже не нужен. Он отлично компенсируется использованием запечатанных типов и переключателей, использующих сопоставление с шаблоном – в таком случае те же цели достигаются проще и меньшим объемом кода. Всякий раз, оказываясь в ситуации, где мог бы применяться паттерн Посетитель, подумайте, не воспользоваться ли вместо него более современными возможностями языка   Java. Разумеется, эти возможности могут использоваться и в других обстоятельствах, но в этой статье мы обсудим сравнительно узкую тему: чем заменить паттерн Посетитель. Для этого я начну с максимально краткого введения и приведу пример, а затем объясню, как достичь тех же целей более простым (и кратким) кодом.  Википедия сообщает: Паттерн проектирования «Посетитель» позволяет открепить алгоритм от структуры того объекта, которым он оперирует. Практический результат такого открепления – способность добавлять новые операции к имеющимся структурам объектов, не модифицируя эти структуры. Основная мотивация здесь – именно в том, чтобы не изменять структуру. Если над объектом производится много операций, либо производимые над ним операции сильно отличаются, то реализация их на уже вовлеченных типах может легко перегрузить типы массой функций, не связанных друг с другом. Разумеется, изменить эти типы получится лишь в том случае, если они не обременены зависимостями. Основная мотивация: не изменять типы. При использовании паттерна «Посетитель» каждая операция реализуется в Посетителе так, что затем она передается структуре объектов, а та передает Посетителю те объекты, из которых состоит. Структура ничего не знает ни об одном конкретном Посетителе, поэтому может свободно создаваться всякий раз, когда понадобится та или иная операция.  Вот пример из Википедии (немного сокращенный): Тут есть целый ряд вещей, которые я сделал бы иначе (Car наследует CarElement? Серьезно?!), но, чтобы не усложнять сравнения, я решил как можно ближе придерживаться оригинала. Про паттерн Посетитель написано уже очень много (варианты использования, предпосылки, реализация, ограничения, т.д.), поэтому нет необходимости все это здесь повторять. Давайте просто предположим, что оказались в ситуации, где использовать этот паттерн действительно целесообразно. Так вот, что мы применили бы вместо него. В современном языке Java есть более удобные способы достичь тех целей, для которых предназначается паттерн Посетитель – поэтому он становится избыточен.  Основная задача паттерна «Посетитель» - обеспечить реализацию нового функционала, такого, который тесно связан с коллекцией типов, но: Не изменяя этих типов (кроме одноразовой настройки новой конфигурации). При этом обеспечивая удобство поддержки того кода, который получится в результате. Это достигается так: Вы можете создавать отдельную реализацию Посетителя для каждой операции (не трогая тех типов, над которыми она производится). От каждого Посетителя требуется, чтобы он мог обрабатывать все релевантные классы (в противном случае они не скомпилируются). Вот эта часть паттерна: Благодаря нововведениям языка Java, теперь эти цели достигаются гораздо проще: Создаем запечатанный интерфейс для всех типов, участвующих в этих операциях. Всякий раз, когда требуется новая операция, используем паттерны типов в режиме переключения, чтобы реализовать эту возможность (существует в виде превью в Java 17). Запечатанный интерфейс, переключатель и сопоставление с шаблоном: Разберем по порядку: switch(element) переключает код с элемента на элемент. На каждом case проверяется, относится ли данный экземпляр к указанному типу  Если так – то под новым именем создается переменная этого типа. Затем переключение результирует в строку, расположенную справа от стрелки. Переключение switch обязательно должно результировать в result, который затем присваивается message. Вот это "обязательно результировать в result" работает и без ветки по умолчанию (default), так как CarElement запечатан, что и дает знать компилятору (а также вашим коллегам), что только перечисленные типы напрямую его реализуют. Компилятор может применить это знание при переключении паттернов и определить, что перечисленные случаи являются исчерпывающими, то есть, что код был проверен для всех возможных реализаций. Так что, когда вы добавляете новый тип к запечатанному интерфейсу, все переключения паттернов без ветки default вдруг станут неисчерпывающими и станут приводить к ошибкам компиляции. Подобно тому, как если бы мы добавили новый метод visit к интерфейсу Посетителя, в данном случае хорошо, что у нас стали возникать эти ошибки компиляции – ведь они приведут вас туда, где вам понадобится изменить ваш код, чтобы обработать новый случай. Поэтому, вероятно, нее стоит добавлять ветку default к таким переключателям. Если есть такие типы, оперировать которыми вы заведомо не хотите, то перечислите их явно: (Если вы очень пристально следили за добавлением новых возможностей, то, возможно, считаете, что все это превосходно – но, на самом деле, это подходит только к переключателям, поскольку для инструкций (statements) не проверяется, являются ли они исчерпывающими. К счастью, в соответствии с предложением  JEP 406, для всех операций переключения паттернов должна проверяться их полнота, независимо от того, как именно они используются – в виде инструкции или в виде выражения.  Паттерн «Посетитель» реализует внутреннюю итерацию. Значит, вместо того, чтобы каждый пользователь структуры данных реализовывал с ней собственный вариант перебора (в коде, который напишет сам, вне этой структуры данных – следовательно, это была бы внешняя итерация), это действие передается на выполнение самой структуре данных, которая затем перебирает сама себя (этот код находится в пределах структуры данных, следовательно, является внутренним) и применяет действие: Здесь мы пользуемся многоразовым применением итерационной логики, что особенно интересно в случаях, чуть менее тривиальных, чем прямой цикл. Недостаток в том, что такому коду приходится покрывать множество конкретных случаев использования перебора: находить результат, вычислять новые значения и собирать из них список, сокращать значения до единственного результата, т.д. Думаю, вы понимаете, к чему я клоню: потоки Java уже делают все это и не только! Поэтому, чтобы не реализовывать импровизированного варианта Stream::forEach, почему бы не взять такой дельный вариант? Использование потоков для внутреннего перебора: Так переиспользуется более мощный и хорошо понятный API, значительно упрощающий любые операции, которые не сводятся к простому Stream::forEach! Теперь давайте целиком соберем получившееся у нас решение: Функционал все тот же, но количество строк кода уменьшилось вдвое, и не осталось никакой косвенности. Неплохо, правда?  На мой взгляд, этот подход обладает целым рядом достоинств по сравнению с паттерном «Посетитель».  В целом все решение получилось гораздо проще. Никакого интерфейса посетителя, не требуются классы посетителя, нет двойной диспетчеризации, и никаких криво поименованных методов, разбросанных по всему коду. Такой код становится не только проще закладывать и расширять; в данном случае разработчикам еще и не приходится учить конкретный паттерн, чтобы понять, что происходит. Значительно сократилась косвенность, поэтому такой код вы можете просто читать с листа. Переделывать его тоже проще: всего лишь сделайте общий запечатанный интерфейс – и в путь. Паттерн «Посетитель» требует реализовать внутренний механизм итерации, который, как я уже указал, прост только в самых простых случаях. Если работать с Потоками, то найдется масса уже готового функционала, при помощи которого удобно вычислять результат. И, чего не скажешь о паттерне «Посетитель», это можно сделать, не создавая экземпляр, не изменяя состояния (большой кусок работы) – и не делая запроса в конце: Да, трюк немного дешевый, но мою мысль вы поняли. В настоящее время у нас есть только паттерны типов, но скоро появится и много новых, и их можно будет использовать для реализации более детальной обработки посещенного элемента прямо на месте: Так у нас появляется возможность связать всю или почти всю логику диспетчеризации в одном месте, а не рассеивать ее по множеству методов, как пришлось бы поступить в случае с «Посетителем». Еще интереснее, что можно разделить диспетчеризацию не только по типу, но и по совершенно другим свойствам: Вместо использования паттерна «Посетитель»: Делаем запечатанный интерфейс для типов, содержащихся в структуре. При операциях используем переключение паттернов – так легко сможем определить в коде путь для каждого типа. Избегаем использования веток default, чтобы при каждой операции у нас возникали ошибки компиляции там, где следует добавить новый тип. Современный Java – выбор победителей! Обратите внимание на майскую распродажу!