Home > Ruby Notes > Visitor Pattern

Visitor Pattern

Tags:  

What is the Visitor Pattern?


see the book Design Patterns, or wikipedia: Visitor Pattern: Visitor Pattern

Is the Visitor Pattern simply the #each method?


One site suggests Visitor in Ruby is simply the Enumerator#each method.  But then the site describes the Visitor pattern as a way to traverse a collection.  Sorry, but that isn't the definition of a Visitor.  The Visitor is defined by the following statement of intent:

Represent an operation to be performed on the elements of an object structure.  Visitor lets you define a new operation without changing the classes of the elements on which it operates.

The each operation only cover how to traverse an Enumerable, it doesn't address how to represent the operation, nor how to add new operations without changing the classes of the elements.  Therefore, I don't believe Enumerator#each is a valid implementation of the Visitor Pattern.

How can you implement the Visitor Pattern in Ruby?

Ruby can leverage message passing to make the Visitor Pattern simplier and more resilent.

Lets start with the following class hierarchy...

  module Namespace
    class A
      include Visitable
    end
    class B < A; end
    class C < A; end
    class D < A; end
  end

I put all the classes (A,B,C,D) inside a module called Namespace because I'm going to show a minor problem caused by this module.

Class A, the top of this hierarchy, includes the mixin module Visitable.

  module Visitable
    def accept( visitor )
      visitor.send( "visit_#{self.class.name}".to_sym, self )
    end
  end

This module defines one method #accept.  Vistable#accept is the first step in the pattern's dual-dispatch.  The implementation is very simple, it send the message visit_Class_Name to the visitor.  For example if we call B#accept, it will in turn call visitor.visit_Namespace::B( self ).  Notice the method called is based on the class, thus we accomplish the second step in the pattern's dual-dispatch.

Here we have reached the problem with the Namespace module.  The method visit_Namespace::B is difficult to define in Ruby:

  class ExampleVisitor
    def visit_Namespace::B( object ) #=> syntax error because of '::'
    end
  end

To get around this problem, we can use #define_method

  class ExampleVisitor
    define_method( "visit_Namespace::B".to_sym ) do | object |
      #...
    end
  end

Now lets define a Visitor class.  In this example, the Visitor will report the class name of the object.  For variety, the B class is reported differently.

   class NameVisitor
    define_method( "visit_Namespace::B".to_sym ) do |object|
      puts "The super cool #{object.class.name}"
    end
    def method_missing(method, *args)
      visit( args[0] )
    end
 
    def visit( object )
      puts object.class.name
    end
  end

Notice the implementation above, we have special behavior for the Namespace::B class; plus we can use method_missing to route all other classes to the default behavior defined in #visit.


Using the visitor is accomplished by calling #accept.  For example:
  objects = [ Namespace::A.new, Namespace::B.new, Namespace::C.new, Namespace::D.new ]
name_visitor = NameVisitor.new
objects.each { |o| o.accept( name_visitor ) }

The advantage is the message passing now avoids the maitenance issue with the Visitor pattern.  Updates to the Visitable class hierarchy no longer require an update to the Visitor(s).  Plus we have accomplished the goal of the Visitor Pattern by separating the extra methods to their own class.  This eliminates the need for variations of the Visitor Pattern such as the Acyclic Visitor.

Can you eliminate Visitable#accept?

Yes, as suggest in this java article you could move the #accept logic into #visit.  This eliminates the need for the Visitable module.  Here is the NameVisitor rewritten:

  class NameVisitor
def visit( object )
self.send( "visit_#{object.class.name}".to_sym, object )
end

define_method( "visit_Namespace::A".to_sym ) do |object|
puts "The one and only #{object.class.name}"
end

def method_missing(method, *args)
puts args[0].class.name
end
end

The usage changes from calling Visitable#accept to Visitor#visit:

  objects = [ Namespace::A.new, Namespace::B.new, Namespace::C.new, Namespace::D.new ]
name_visitor = NameVisitor.new
objects.each { |o| name_visitor.visit(o) }

What alternatives are available in Ruby?

To answer that question, let first consider some alternatives:

Monkey Patching or Duck Punching

Certainly Monkey Patching is an alternative to using Visitor Pattern.  But Monkey Patching does have its own pitfalls.  Monkey Patching is eaiser to accomplish.  One problem with Monkey Patching is it's easy to use private member inside the patch.  This increases the dependency and makes it very easy for a patch to break when the target of the patch is updated.  The Visitor Pattern uses a separate class, so it is restricted to using public methods on the target.

Mixins

Using a Module to mixin new methods could help organize the changes.  But it doesn't easily provide different behavior for different classes.  It is possible to create a parallel hierarchy of mixin modules, but that is an ugly solution.

Decorator Pattern

The Decorator Pattern is a viable choice, which is also easy to implement in Ruby.  The Decorator looses the ability to keep state between visits.  A Visitor has the ability to accumulate results, while the Decorator can only modify the behavior.

What about the Hierarchical Visitor Pattern?

The Heirarchical Visitor adds the ability to control which elements are visited, as well as feedback about nesting and depth.

The implementation needs the Collection being visited upon to implement the #accept with conditional logic that controls how to traverse the collection.  The #visit____ methods would collaborate with the #accept methods by returning true or false to #accept's conditions.  TODO... how could a dynamic langauge make this easier or simplier?

A first attempt at implementing a Hierarchical Visitor follows.  It builds on the Namespace module listed above.  I am not very satisfied with this solution.  It is just a straight port of the documented pattern into Ruby.

module HierarchyVisitable
def accept( visitor )
if ( visitor.visit_enter( self ) ) then
self.each do | child |
unless child.accept( visitor ) then
break;
end # if
end # self.each
end # if
visitor.visit_exit( self )
end #def
end #module

class Array
include HierarchyVisitable
end

module Namespace
class NameVisitor
def visit_enter( object )
puts "enter: #{object.inspect}"
true
end

def visit_exit( object )
puts "exit: #{object.inspect}"
true
end

def method_missing(method, *args)
visit( args[0] )
end

def visit( object )
puts "visit: #{object.class.name}"
true
end
end # class

objects = [A.new, [B.new, C.new], [D.new, [B.new, C.new]]]
v = NameVisitor.new
objects.accept( v )
end

The output is:

enter: [#<Namespace::A:0x282d1a4>, [#<Namespace::B:0x282d17c>, #<Namespace::C:0x282d168>], [#<Namespace::D:0x282d12c>, [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]]]
visit: Namespace::A
enter: [#<Namespace::B:0x282d17c>, #<Namespace::C:0x282d168>]
visit: Namespace::B
visit: Namespace::C
exit: [#<Namespace::B:0x282d17c>, #<Namespace::C:0x282d168>]
enter: [#<Namespace::D:0x282d12c>, [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]]
visit: Namespace::D
enter: [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]
visit: Namespace::B
visit: Namespace::C
exit: [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]
exit: [#<Namespace::D:0x282d12c>, [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]]
exit: [#<Namespace::A:0x282d1a4>, [#<Namespace::B:0x282d17c>, #<Namespace::C:0x282d168>], [#<Namespace::D:0x282d12c>, [#<Namespace::B:0x282d104>, #<Namespace::C:0x282d0f0>]]]





 RSS of this page