Глянь мой новый курс по 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