views:

112

answers:

3

Before you tell me that there is already a similar question, yes, i know, I've read it. But the question there focuses on when, I'm interested in why.

I get how the things work. The classic animal,dog,cat example always works like a charm.

The thing is this code

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

seems so unnatural to me. Why?

I mean, yeah, this way I have my Dog and Cat models undifferentiated (first time I ever use that word in english btw) because the real implentation is hidden under the Sound class but isn't that just a way to weigh down your code? Isn't polymorphism enough to do something like this?

To me the difference is that with polymorphism you have to edit each class (but the model stays the same, right?) whereas you have just to edit one class with the visitor design pattern.

+5  A: 

The visitor pattern allows you to do something, which simply relying on polymorphism does not: work with unanticipated use cases. If you are writing a library, this is an important point. Let me elaborate:

Consider a classical example for the use of the visitor pattern, namely, the operation on the nodes of some abstract syntax tree. To add some details, say, you have just written a parser library for SQL, which takes strings, parses them, and returns an AST for the stuff it found in the input. Unless you can anticipate all potential use cases your client code might have for such an AST, you have to provide a "generic" way to walk the AST. Providing DOM-like accessor functions (getNodeType, getParentNode, getPreviousNode) is one way. The problem here is, that this puts a heavy burden on the clients of your library, because they need to do the dispatch themselves. Even more, they need to know in great detail, which pointers to follow for each possible node type:

void 
walk_tree(AstNode* node) 
{
    switch( node->getNodeType() ) {
    case SELECT_NODE:
        for( AstNode* child = node->getFirstChild(); child; child = child->getNextNode() ) {
             walk_tree(child);
        }
        break;
    ...
    }
}

The visitor pattern moves this burden from the client into the library.

Dirk
Exactly, the behavior can be implemented by a 3rd party.
Kirk Woll
Ok, but if you define parser for SQL don't you have already your grammar definition? Where are the unanticipated use cases? Actually since we're talking about parsing, shouldn't be a parser generator a more appropriate example? There you have an arbitrary grammar so then you have to define a generic tree walker class.
dierre
It's not about the structure of the SQL. It's about how the client can use the result/output of your library. The parser example is simply used because it is somewhat "classic". BTW, if you have a fully generic API (arbitrary "DOM"-like nodes) without a fixed structure, you might be better off with the generic accessor approach instead of the visitor pattern.
Dirk
+1  A: 

Let's say you've got some basic stuff defined in a library you don't own, and you need to extend it. Like:

// In base lib:
interface ISomething {
    void DoSomething();
}

class Something1 : ISomething {
    // ...
}

class Something2 : ISomething {
    // ...
}

Polymorphism lets you define new things you can perform operations on:

// In your lib:
class MySomething : ISomething {
}

And now the base lib can work with your MySomething as if it had defined it. What it doesn't let you do is add new operations. DoSomething is the only thing we can do with an ISomething. The Visitor pattern addresses that.

The downside is that using the visitor pattern costs you the ability to define new types like we just showed. The fact that most languages let you either add operations or types easily, but not both, is called the expression problem.

The visitor pattern is a cool one, but I've never actually found a need for it outside of implementing compilers.

munificent
You can't add new types because your Visitor will be still a component of the library, right?
dierre
The visitor class will be defined in the base lib and will have a fixed set of methods, one for each type. You can't add new types in your lib because you can't change the visitor, and you can't move the visitor into your lib because the types in the base lib need to reference it in their `accept()` methods.
munificent
got it. thanks!
dierre
A: 

I used the visitor pattern when I had a tree of objects and needed to print the contents in numerous ways. Comma-sep, XML, whatever. Instead of adding to the tree class a new print method for each output format, I used the visitor pattern and created CommaSepVisitor, XMLVisitor, and HTMLVisitor classes. The tree code never changed as I added more Visitor types so I never introduced bugs. The visitors themselves were easy to write.

Tony Ennis