Посетитель на Ruby
Посетитель — это поведенческий паттерн, который позволяет добавить новую операцию для целой иерархии классов, не изменяя код этих классов.
Подробней о том, почему Посетитель нельзя заменить простой перегрузкой методов читайте в статье Посетитель и Double Dispatch.
Сложность:
Популярность:
Применимость: Посетитель нечасто встречается в Ruby-коде из-за своей сложности и нюансов реализазации.
Концептуальный пример
Этот пример показывает структуру паттерна Посетитель, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.rb: Пример структуры паттерна
# Интерфейс Компонента объявляет метод accept, который в качестве аргумента # может получать любой объект, реализующий интерфейс посетителя. class Component # @abstract # # @param [Visitor] visitor def accept(_visitor) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end end # Каждый Конкретный Компонент должен реализовать метод accept таким образом, # чтобы он вызывал метод посетителя, соответствующий классу компонента. class ConcreteComponentA < Component # Обратите внимание, мы вызываем visitConcreteComponentA, что соответствует # названию текущего класса. Таким образом мы позволяем посетителю узнать, с # каким классом компонента он работает. # # @param [Visitor] visitor def accept(visitor) visitor.visit_concrete_component_a(self) end # Конкретные Компоненты могут иметь особые методы, не объявленные в их базовом # классе или интерфейсе. Посетитель всё же может использовать эти методы, # поскольку он знает о конкретном классе компонента. def exclusive_method_of_concrete_component_a 'A' end end # То же самое здесь: visit_concrete_component_b => ConcreteComponentB class ConcreteComponentB < Component # @param [Visitor] visitor def accept(visitor) visitor.visit_concrete_component_b(self) end def special_method_of_concrete_component_b 'B' end end # Интерфейс Посетителя объявляет набор методов посещения, соответствующих # классам компонентов. Сигнатура метода посещения позволяет посетителю # определить конкретный класс компонента, с которым он имеет дело. # # @abstract class Visitor # @abstract # # @param [ConcreteComponentA] element def visit_concrete_component_a(_element) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end # @abstract # # @param [ConcreteComponentB] element def visit_concrete_component_b(_element) raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end end # Конкретные Посетители реализуют несколько версий одного и того же алгоритма, # которые могут работать со всеми классами конкретных компонентов. # # Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со # сложной структурой объектов, такой как дерево Компоновщика. В этом случае было # бы полезно хранить некоторое промежуточное состояние алгоритма при выполнении # методов посетителя над различными объектами структуры. class ConcreteVisitor1 < Visitor def visit_concrete_component_a(element) puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}" end def visit_concrete_component_b(element) puts "#{element.special_method_of_concrete_component_b} + #{self.class}" end end class ConcreteVisitor2 < Visitor def visit_concrete_component_a(element) puts "#{element.exclusive_method_of_concrete_component_a} + #{self.class}" end def visit_concrete_component_b(element) puts "#{element.special_method_of_concrete_component_b} + #{self.class}" end end # Клиентский код может выполнять операции посетителя над любым набором # элементов, не выясняя их конкретных классов. Операция принятия направляет # вызов к соответствующей операции в объекте посетителя. # # @param [Array<Component>] components # @param [Visitor] visitor def client_code(components, visitor) # ... components.each do |component| component.accept(visitor) end # ... end components = [ConcreteComponentA.new, ConcreteComponentB.new] puts 'The client code works with all visitors via the base Visitor interface:' visitor1 = ConcreteVisitor1.new client_code(components, visitor1) puts 'It allows the same client code to work with different types of visitors:' visitor2 = ConcreteVisitor2.new client_code(components, visitor2) output.txt: Результат выполнения
The client code works with all visitors via the base Visitor interface: A + ConcreteVisitor1 B + ConcreteVisitor1 It allows the same client code to work with different types of visitors: A + ConcreteVisitor2 B + ConcreteVisitor2