문제

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.

The Visitor Pattern is very useful.

There are at least three big reasons for using it:

  1. Reduce proliferation of code which is only slightly different when data structures change.

  2. Apply the same computation to several data structures, without changing the code which implements the computation.

  3. Add information to legacy libraries without changing the legacy code.

Please have a look at an article I've written about this.

Cheers

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top