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.
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.
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.
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