Глянь мой новый курс по Git! Привет! Глянь мой новый курс по Git! Привет! Глянь мой новый курс по Git на GitByBit.com! Привет! Хочешь круто подтянуть Git? Глянь мой новый курс на GitByBit.com!
Посетитель

Посетитель на 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 

Посетитель на других языках программирования

Посетитель на C# Посетитель на C++ Посетитель на Go Посетитель на Java Посетитель на PHP Посетитель на Python Посетитель на Rust Посетитель на Swift Посетитель на TypeScript