Существует две версии шаблона посетителя: одна версия выполняет только двойную диспетчеризацию, а другая - итерацию, автоматически посещая дочерние элементы узла. Последняя версия менее гибкая, потому что вы решаете, какой тип обхода (предзаказ или постзаказ) вы хотите заблаговременно, а не оставляете это решение для отдельного посетителя. Это также вынуждает вас посещать все узлы ровно один раз (что вам не нужно во многих случаях, например, при реализации интерпретатора AST).
В вашем коде вы на самом деле реализуете обе эти версии: ваш Visitor#visit
метод реализует шаблон простого посетителя, а ASTNode#accept
реализует шаблон с итерацией. Это странное использование метода accept
, потому что обычно работа метода accept заключается в простом вызове определенного метода visit
(например, visit_whatever
) для посетителя, чтобы заставить работать двойную диспетчеризацию. Поскольку вы использовали отражение для реализации двойной диспетчеризации, вам совершенно не нужен метод accept
.
Я предполагаю, что печать должна быть реализована в методах посещения (субъекта) PrintVisitor
Это правильно.
Для печати каждого узла требуется дополнительный контекст для определения правильного уровня отступа.
Также правильно. Вы можете отслеживать уровень отступов, сохраняя его в переменной экземпляра. Затем данный метод посетителя распечатывает его содержимое с заданным количеством отступов, увеличивает уровень отступа, посещает его дочерние заметки, а затем снова уменьшает отступ. Примерно так:
def visit_SomeNode(some_node)
puts "#{@indent * " "}---SomeNode"
@indent += 4
some_node.children.each {|child| visit(child)}
@indent -= 4
end
Вы также можете поместить some_node.children.each {|child| visit(child)}
в его собственный метод visit_children(node)
и просто вызывать его для тех случаев, когда вы хотите выполнить одно и то же действие для всех детей (как указано выше).
Если вы хотите избежать этого изменяемого состояния, вы также можете настроить свой класс посетителя, чтобы разрешить передачу аргументов в visit
следующим образом:
class Visitor
def visit(subject, *args)
method_name = "visit_#{subject.class}".intern
send(method_name, subject, *args)
end
end
Затем вы можете добавить параметр для уровня отступа в ваши методы и передать увеличенный уровень отступа до visit
при посещении ваших детей.