Is the Visitor pattern useful for dynamically typed languages?
Asked Answered
S

6

10

The Visitor pattern allows operations on objects to be written without extending the object class. Sure. But why not just write a global function, or a static class, that manipulates my object collection from the outside? Basically, in a language like java, an accept() method is needed for technical reasons; but in a language where I can implement the same design without an accept() method, does the Visitor pattern become trivial?

Explanation: In the Visitor pattern, visitable classes (entities) have a method .accept() whose job is to call the visitor's .visit() method on themselves. I can see the logic of the java examples: The visitor defines a different .visit(n) method for each visitable type n it supports, and the .accept() trick must be used to choose among them at runtime. But languages like python or php have dynamic typing and no method overloading. If I am a visitor I can call an entity method (e.g., .serialize()) without knowing the entity's type or even the full signature of the method. (That's the "double dispatch" issue, right?)

I know an accept method could pass protected data to the visitor, but what's the point? If the data is exposed to the visitor classes, it is effectively part of the class interface since its details matter outside the class. Exposing private data never struck me as the point of the visitor pattern, anyway.

So it seems that in python, ruby or php I can implement a visitor-like class without an accept method in the visited object (and without reflection), right? If I can work with a family of heterogeneous objects and call their public methods without any cooperation from the "visited" class, does this still deserve to be called the "Visitor pattern"? Is there something to the essence of the pattern that I am missing, or does it just boil down to "write a new class that manipulates your objects from the outside to carry out an operation"?

PS. I've looked at plenty of discussion on SO and elsewhere, but could not find anything that addresses this question. Pointers welcome.

Stieglitz answered 22/6, 2012 at 10:43 Comment(3)
Reading your question carefully, I see that my answer is a long-winded way of agreeing with you.Ontology
Ha, thanks :-) It's good that this question is open again... More than two years after asking it, I'm still not sure what the remaining essence of the Visitor pattern is, after it "all but disappears" (as wikipedia put it) in python and similar languages...Stieglitz
I think the remaining essence is basically an iterated strategy pattern. But then, Visitor was always that, with double-dispatch tricks to handle different types.Ontology
I
1

This answer is made with an ignorance of PHP etc but the Visitor needs typically to call more than just a single method (you mentioned "serialize") on the entities. When the Visit() method is called on the concrete Visitor, the Visitor is capable of running distictly different code for each entity subtype. I don't see how that is different from a dynamically-types language (though I'd love some feedback).

Another nice benefit of Visitor is it provides a clean seperation of the code that is getting run on each entity from the code that enumerates the entities. This has saved me some serious code duplication in at least one large project.

As an aside, I've used Visitor in languages that did not have method overloading. You just replace Visit(TypeN n) with VisitN(TypeN n).


Follow up from comments.

This is a visitor psuedo code, and I don't know how I would so it without the cooperation of the visited object (at least without a switch block):

abstract class ScriptCommand
{
   void Accept(Visitor v);
}

abstract class MoveFileCommand
{
   string TargetFile;
   string DestinationLocation;

   void Accept(Visitor v)
   {
      v.VisitMoveFileCmd(this);  // this line is important because it eliminates the switch on object type
   }
}

abstract class DeleteFileCommand
{
   string TargetFile;

   void Accept(Visitor v)
   {
      v.VisitDeleteFileCmd(this); // this line is important because it eliminates the switch on object type

   }
}

// etc, many more commands

abstract class CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd);
   void VisitDeleteFileCmd(DeleteFileCommand cmd);
   // etc
}

// concrete implementation

class PersistCommandVisitor() inherits CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      // save the MoveFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // save the DeleteFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

}

The visitor infrastructure allows the handling of a wide array of command subtypes with no select case, swithc, if else.

In regards to the visitor handling the enumerating, I think you are limiting yourself like that. That's not to say a cooperating class (an abstract VisitorEnumerator) can't be involved.

For example, note this visitor is unaware of the order of enumeration:

class FindTextCommandVisitor() inherits CommandVisitor
{
   string TextToFind;
   boolean TextFound = false;

   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      if (cmd.TargetFile.Contains(TextToFind) Or cmd.DestinationLocation.Contains(TextToFind))
         TextFound = true;
   }


   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // search DeleteFileCommand's properties
   }

}

And this allows it to be reused like this:

ScriptCommand FindTextFromTop(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = 0; cmdNdx < CommandList.Length; cmdNdx++)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

and the enumerate the opposite way with the same visitor:

ScriptCommand FindTextFromBottom(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = CommandList.Length-1; cmdNdx >= 0; cmdNdx--)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

In real code I would create a base class for the enumerator and then subclass it to handle the different enumeration scenarios, while passing in the concrete Visitor subclass to completely decouple them. Hopefully you can see the power of keeping the enumeration seperate.

Ivanaivanah answered 22/6, 2012 at 13:24 Comment(10)
The point of my question is that you can write visitN() for each type, and you can call various object methods directly, but none of this requires the cooperation of the visited object. How is the visitor pattern different from any global function that performs an operation on objects? As for enumeration: Many consider it best practice for the visitor to handle the enumeration.Stieglitz
I see. So the visitor must provide a menu of methods, and the visited object chooses from them (explicitly, in this case), instead of the visitor providing a type-to-method map. And all visitors must name their methods the same, right?Stieglitz
Incidentally I found your choice of example unfortunate: The visitor normally represents an operation on a class of objects, but your objects are themselves operations. I'm imagining they represent GUI elements rather than actions, but I found this use case needlessly confusing. Just saying.Stieglitz
You would typically have a Visitor base class or interface and inherit the methods, which enforces they are named the same in the concrete Visitor subclasses.Ivanaivanah
In regards to my choice of example, the whole point of the concept of patterns is that they work across domains. A visitor is equally valid when used with GUI elements as it is with a script interpreter. Though if it makes you feel better, the script interpreter I modeled the example from is graphical and the operations like FindText are used to locate and highlight the graphical representation of the command. Sorry it was confusing though.Ivanaivanah
Ok, thanks. But I still wonder: If the collection of manipulated classes is not going to change (important precondition for the visitor pattern, everyone says), is a type-to-method map really worse than spreading around all this complexity? What's the big benefit?Stieglitz
let us continue this discussion in chatIvanaivanah
SO whined so I let it move to chat (which I've never used). Further comments are in that linkIvanaivanah
The problem with this answer is that it doesn't actually address dynamic languages. Dynamic languages (with untyped variables) don't have the ability (as a general rule) to select between methods based on the type of arguments to the methods. This means that to replicate the traditional "dispatch in visitee" style of Visitor pattern, the visitor needs to provide differently named methods; this reproduces the code smell which Visitor is designed to avoid.Ontology
Please also read the linked discussion in chat, as it does discuss your point. As discussed in there in more depth, Visitor has more than an single benefit, and it still has value in a dynamically-typed language...just not as much as in a statically-typed language. Your example below is a bit contrived because you are doing the same thing with each of the process methods. In the script-command sample above, the logic in each differs widely and vastly, and would require your NonVisitorVisitor to then use a switch or other mechanism to demultiplex the fruit parameter.Ivanaivanah
O
2

The place where Visitor is particularly useful is where the Visitor needs to switch on the type of Visitees, and for whatever reason, you don't want to encode that knowledge into the Visitees (think plugin architectures). Consider the following Python code:

Visitor style

class Banana(object):
      def visit(self, visitor):
          visitor.process_banana(self) 

class Apple(object):
      def visit(self, visitor):
          visitor.process_apple(self) 

class VisitorExample(object):
      def process_banana(self, banana):
          print "Mashing banana: ", banana

      def process_banana(self, apple):
          print "Crunching apple: ", apple

(Note that we could compress the visitee logic with a base class/mixin).

Compare with:

Non-visitor style

class NonVisitorVisitor(object):
      def process(self, fruit):
          verb = {Banana: "Mashing banana: ", 
                  Apple: "Crunching apple: "}[type(fruit)]
          print verb, fruit

In the second example, the fruits don't need any special support for the "visitor", and the "visitor" handles the absence of logic for the given type.

By contrast, in Java or C++ the second example is not really possible, and the visit method (in the visitees) can use one name to refer to all versions of the process method; the compiler will pick the version which applies to the type being passed; and the visitor can easily provide a default implementation for the root class for the type of visitees. It's also necessary to have a visit method in the visitees because the method variant (e.g. process(Banana b) vs process(Apple a)) is selected at compile time in the code generated for the visitee's visit method.

Accordingly, in languages like Python or Ruby where there is no dispatch on parameter types (or rather, the programmer has to implement it themselves), there is no need for the visitor pattern. Alternatively, one might say the visitor pattern is better implemented without the dispatching through visitee methods.

In general in dynamic languages like Python, Ruby, or Smalltalk, it is better to have the "visitee" classes carry all the information needed (here, the verb applicable), and if necessary, provide hooks to support the "visitor", such as command or strategy patterns, or use the Non-visitor pattern shown here.

Conclusion

The non-Visitor is a clean way to implement the type-switching logic, notwithstanding that explicit type switching is usually a code smell. Remember that the Java and C++ way of doing it is also explicit switching in the Visitor; the elegance of the pattern in those languages is that it avoids having explicit switching logic in the visitees, which is not possible in dynamic languages with untyped variables. Accordingly, the Visitor pattern at the top is bad for dynamic languages because it reproduces the sin which the Visitor pattern in static languages seeks to avoid.

The thing with using patterns is that rather than slavishly reproducing UML diagrams, you must understand what they are trying to accomplish, and how they accomplish those goals with the language machinery concretely under consideration. In this case, the pattern to achieve the same merits looks different, and has a different pattern of calls. Doing so will allow you to adapt them to different languages, but also to different concrete situations within the same language.

Update: here's a ruby article on implementing this pattern: http://blog.rubybestpractices.com/posts/aaronp/001_double_dispatch_dance.html

The double dispatch seems rather forced to me; you could just do away with it, as far as I can tell.

Ontology answered 17/4, 2015 at 10:48 Comment(6)
I see... so the Example class implements a bunch of basic behaviors (perhaps standard display components like tables or lists), then tells the visitees "display yourself" and they use the visitor's methods to do so? And another visitor could provide different implementations, e.g. PDF vs. HTML... am I on the right track? It's years since I asked the question, but I'm still not clear on the proper usage...Stieglitz
I meant that one visitee might choose to display itself as a list, another as a table, etc.; the visitor knows how to draw a table, but not when it is appropriate. But I'm not sure anymore if this matches what you had in mind.Stieglitz
@Stieglitz I see what you mean. What you're describing is - I think - not really the visitor pattern but a form of the strategy pattern. Actually, I think this is the problem with patterns talk as it exists now- it really focuses on what amount to UML diagram patterns in quite a concrete way, without focusing on the best practices they embody, or identifying how the patterns may or may not compensate for, or relate to, specific language deficiencies/features. A visitor-strategy pattern makes sense; but again that's another weakness of patterns talk: it doesn't discuss how patterns relate.Ontology
Thanks! I'll refresh my memory of the strategy pattern then. Coming back to this question has been helpful: the scenario I described matches an application I'm working on (though with just one output format).Stieglitz
Now it seemed to me that in the real visitor pattern, the visitor knows what method it wants to call, but the visitees know what data they want to provide.. but that's not what your example shows. In your example, the visitor provides the methods and the visit amounts to telling the visitees "call my methods". If the visitees "know" which method of the visitor they should call, how is this different from the strategy pattern?Stieglitz
@Stieglitz In Java/C++ the type of the visitee selects the right method to call; in my example, the visitee has to specify its type with the call. The difference with strategy is that visitee is supposed to only call the method corresponding to its own type. My problem with the GOF patterns is that they aren't necessarily super distinct, and mix up a bunch of different considerations.Ontology
I
1

This answer is made with an ignorance of PHP etc but the Visitor needs typically to call more than just a single method (you mentioned "serialize") on the entities. When the Visit() method is called on the concrete Visitor, the Visitor is capable of running distictly different code for each entity subtype. I don't see how that is different from a dynamically-types language (though I'd love some feedback).

Another nice benefit of Visitor is it provides a clean seperation of the code that is getting run on each entity from the code that enumerates the entities. This has saved me some serious code duplication in at least one large project.

As an aside, I've used Visitor in languages that did not have method overloading. You just replace Visit(TypeN n) with VisitN(TypeN n).


Follow up from comments.

This is a visitor psuedo code, and I don't know how I would so it without the cooperation of the visited object (at least without a switch block):

abstract class ScriptCommand
{
   void Accept(Visitor v);
}

abstract class MoveFileCommand
{
   string TargetFile;
   string DestinationLocation;

   void Accept(Visitor v)
   {
      v.VisitMoveFileCmd(this);  // this line is important because it eliminates the switch on object type
   }
}

abstract class DeleteFileCommand
{
   string TargetFile;

   void Accept(Visitor v)
   {
      v.VisitDeleteFileCmd(this); // this line is important because it eliminates the switch on object type

   }
}

// etc, many more commands

abstract class CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd);
   void VisitDeleteFileCmd(DeleteFileCommand cmd);
   // etc
}

// concrete implementation

class PersistCommandVisitor() inherits CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      // save the MoveFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // save the DeleteFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

}

The visitor infrastructure allows the handling of a wide array of command subtypes with no select case, swithc, if else.

In regards to the visitor handling the enumerating, I think you are limiting yourself like that. That's not to say a cooperating class (an abstract VisitorEnumerator) can't be involved.

For example, note this visitor is unaware of the order of enumeration:

class FindTextCommandVisitor() inherits CommandVisitor
{
   string TextToFind;
   boolean TextFound = false;

   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      if (cmd.TargetFile.Contains(TextToFind) Or cmd.DestinationLocation.Contains(TextToFind))
         TextFound = true;
   }


   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // search DeleteFileCommand's properties
   }

}

And this allows it to be reused like this:

ScriptCommand FindTextFromTop(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = 0; cmdNdx < CommandList.Length; cmdNdx++)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

and the enumerate the opposite way with the same visitor:

ScriptCommand FindTextFromBottom(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = CommandList.Length-1; cmdNdx >= 0; cmdNdx--)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

In real code I would create a base class for the enumerator and then subclass it to handle the different enumeration scenarios, while passing in the concrete Visitor subclass to completely decouple them. Hopefully you can see the power of keeping the enumeration seperate.

Ivanaivanah answered 22/6, 2012 at 13:24 Comment(10)
The point of my question is that you can write visitN() for each type, and you can call various object methods directly, but none of this requires the cooperation of the visited object. How is the visitor pattern different from any global function that performs an operation on objects? As for enumeration: Many consider it best practice for the visitor to handle the enumeration.Stieglitz
I see. So the visitor must provide a menu of methods, and the visited object chooses from them (explicitly, in this case), instead of the visitor providing a type-to-method map. And all visitors must name their methods the same, right?Stieglitz
Incidentally I found your choice of example unfortunate: The visitor normally represents an operation on a class of objects, but your objects are themselves operations. I'm imagining they represent GUI elements rather than actions, but I found this use case needlessly confusing. Just saying.Stieglitz
You would typically have a Visitor base class or interface and inherit the methods, which enforces they are named the same in the concrete Visitor subclasses.Ivanaivanah
In regards to my choice of example, the whole point of the concept of patterns is that they work across domains. A visitor is equally valid when used with GUI elements as it is with a script interpreter. Though if it makes you feel better, the script interpreter I modeled the example from is graphical and the operations like FindText are used to locate and highlight the graphical representation of the command. Sorry it was confusing though.Ivanaivanah
Ok, thanks. But I still wonder: If the collection of manipulated classes is not going to change (important precondition for the visitor pattern, everyone says), is a type-to-method map really worse than spreading around all this complexity? What's the big benefit?Stieglitz
let us continue this discussion in chatIvanaivanah
SO whined so I let it move to chat (which I've never used). Further comments are in that linkIvanaivanah
The problem with this answer is that it doesn't actually address dynamic languages. Dynamic languages (with untyped variables) don't have the ability (as a general rule) to select between methods based on the type of arguments to the methods. This means that to replicate the traditional "dispatch in visitee" style of Visitor pattern, the visitor needs to provide differently named methods; this reproduces the code smell which Visitor is designed to avoid.Ontology
Please also read the linked discussion in chat, as it does discuss your point. As discussed in there in more depth, Visitor has more than an single benefit, and it still has value in a dynamically-typed language...just not as much as in a statically-typed language. Your example below is a bit contrived because you are doing the same thing with each of the process methods. In the script-command sample above, the logic in each differs widely and vastly, and would require your NonVisitorVisitor to then use a switch or other mechanism to demultiplex the fruit parameter.Ivanaivanah
A
0

Maybe, it depends on the language.

The visitor pattern solves double and multiple-hierarchy problems in languages that don't feature multiple-dispatch. Take Ruby, Lisp and Python. They are all dynamically-typed languages, but only CLOS-Lisp implements multiple-dispatch in the standard. This is also called multimethods and Python and Ruby can apparently implement it by using extensions.

I like this curious comment on wikipedia stating that:

Lisp's object system [CLOS] with its multiple dispatch does not replace the Visitor pattern, but merely provides a more concise implementation of it in which the pattern all but disappears.

In other languages, even statically typed ones, you have to work around the absence of multimethods. The Visitor pattern is one such way.

Anastasius answered 22/6, 2012 at 11:19 Comment(1)
I actually don't think that visitor meaningfully solves the double dispatch problem in languages which can't do any kind of dispatch on parameter types; this is why it works in C++, Java, and C# but not in Python or Ruby.Ontology
A
0

I think you are using Visitor Pattern and Double Dispatch interchangeably. When you say,

If I can work with a family of heterogeneous objects and call their public methods without any cooperation from the "visited" class, does this still deserve to be called the "Visitor pattern"?

and

write a new class that manipulates your objects from the outside to carry out an operation"?

you are defining what Double dispatch is. Sure, Visitor pattern is implemented by double dispatch. But there is something more to the pattern itself.

  • Each Visitor is an algorithm over a group of elements (entities) and new visitors can be plugged in without changing the existing code. Open/Closed principle.
  • When new elements are added frequently, Visitor pattern is best avoided
Ashlan answered 22/6, 2012 at 12:6 Comment(0)
B
0

Visitor Pattern to me meant to add new functionality to objects based on their type. Apparently having if/else ladders to perform type specific operations is bad (I would like an explanation for this :( ). In python, I was able to do this, without the whole double dispatch drama, by monkeypatching (another bad idea) certain functions as class methods.

I asked about this here.

In the below example, assume there's a base class ASTNode and a large class hierarchy under it (ASTVar, ASTModule, ASTIf, ASTConst, etc). These classes only have their specific data attributes and trivial methods.

Then, assume the class code is locked (or maybe the functionality is separated from the data). Now, I have methods which are dynamically assigned to classes. Note that in below example the iteration/recursion method call name (stringify) is different from the function name (nodeType_stringify).

def ASTNode__stringify(self):
    text = str(self)
    for child in self.children:
            text += ", { " + child.stringify() + " }"
    return text

def ASTConst__stringify(self):
    text = str(self)
    for child in self.children:
            text += ", [ " + child.stringify() + " ]"
    return text

def ASTIf__stringify(self):
    text = str(self)
    text += "__cond( " + self.op1.stringify() + ")"
    text += "__then { " + self.op2.stringify() + "}"
    text += "__else {" + self.op3.stringify() + "}"
    return text

I can extend the classes (possibly, one-time during module init) with functionality whenever I want (bad idea?).

# mainModule1.py
def extend_types():
    # ASTNode and all derived class get this method
    ASTNode.stringify = ASTNode__stringify
    ASTConst.stringify = ASTConst__stringify
    ASTIf.stringify = ASTIf__stringify

Now, calling my_root_node.stringify() would appropriately call the right child methods (recursively), without explicitly checking for type.

Isn't this technique be similar to adding methods to the Javascript prototypes (Visitor pattern in JS).

Wasn't this what the goal of Visitor Pattern was? Extension of code-locked Types? Surely, the need to use double-dispatch (VisitorObject.visit(ConcreteObject) being called by ConcreteObject.Accept(VisitorObject)) wouldn't be necessary in python, which is dynamically typed. Probably, someone will formalize this for dynamically typed languages, and we will have a new pattern on hand, or not. After all, patterns are discovered, not invented (I don't remember where I read this).

Birkle answered 1/10, 2016 at 2:1 Comment(0)
L
0

Visitor pattern do 2 things:

  • Allows for ad hoc polymorphism (same function but do different things to different "types").
  • Enables adding new consuming algorithm without changing provider of data.

You can do second in dynamic languages without Visitor nor runtime type information. But first one requires some explicit mechanism, or design pattern like Visitor.

Lengthen answered 23/11, 2017 at 6:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.