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.
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:
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:
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With