CODESAMPLE

Visitor - Ruby

Share on:

The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. It achieves this by defining a separate “visitor” class that implements the operation for each type of object in the hierarchy. The objects “accept” the visitor, allowing the visitor to perform its operation on them.

This Ruby implementation uses a simple Element hierarchy (Node, Leaf) and a Visitor interface with visit_node and visit_leaf methods. Each element type has an accept method that takes a visitor and calls the appropriate visit_ method on it. The ConcreteVisitor performs a specific operation (in this case, counting the number of nodes and leaves) and demonstrates how new operations can be added without altering the element classes. Ruby’s dynamic dispatch and duck typing make the Visitor pattern a natural fit, avoiding the need for explicit interface declarations.

# Element interface
module Element
  def accept(visitor)
    raise NotImplementedError, "#{self.class} must implement #accept"
  end
end

# Concrete Element: Node
class Node
  include Element

  attr_reader :name, :children

  def initialize(name, children = [])
    @name = name
    @children = children
  end

  def accept(visitor)
    visitor.visit_node(self)
    @children.each { |child| child.accept(visitor) }
  end
end

# Concrete Element: Leaf
class Leaf
  include Element

  attr_reader :value

  def initialize(value)
    @value = value
  end

  def accept(visitor)
    visitor.visit_leaf(self)
  end
end

# Visitor interface
module Visitor
  def visit_node(node)
    raise NotImplementedError, "#{self.class} must implement #visit_node"
  end

  def visit_leaf(leaf)
    raise NotImplementedError, "#{self.class} must implement #visit_leaf"
  end
end

# Concrete Visitor: Counter
class Counter
  include Visitor

  attr_reader :node_count, :leaf_count

  def initialize
    @node_count = 0
    @leaf_count = 0
  end

  def visit_node(node)
    @node_count += 1
  end

  def visit_leaf(leaf)
    @leaf_count += 1
  end
end

# Usage example
node1 = Node.new("A", [Leaf.new(1), Node.new("B", [Leaf.new(2)])])
node2 = Node.new("C", [Leaf.new(3)])

counter = Counter.new
node1.accept(counter)
node2.accept(counter)

puts "Nodes: #{counter.node_count}"
puts "Leaves: #{counter.leaf_count}"