Visitor Pattern – Understanding the Purpose of the Accept Method

design-patternsobject-orientedobject-oriented-designvisitor-pattern

I'm trying to fully understand the visitor pattern. What I've learnt so far (correct me if I'm wrong) is:

  • It's about adding operations to classes, without modifying the source code of those classes. Or put another way, to bend the OOP approach to have functions and data structures separated.
  • It's a common misunderstanding that it has to do with hierarchies of objects (although it can be very useful in that case).

I think I get it, but there is a thing that looks unnecessary to me, and that's the accept method in the classes "to be visited". Let's set up a small example in Java. First the class hierarchy to be enriched with operations, but it's not to be modified:

interface Animal {
    void accept(AnimalVisitor visitor);
}

class Dog implements Animal {
    void accept(AnimalVisitor visitor) {
        visitor.visitDog(this);
    }    
}

class Cat implements Animal {
    void accept(AnimalVisitor visitor) {
        visitor.visitCat(this);
    }    
}

Then the visitor interface and a dummy implementation of that interface, representing an operation to make some sound.

interface AnimalVisitor {
    // These methods could be just called "visit" and rely on overloading,
    void visitDog(Dog dog);
    void visitCat(Cat cat);
}

class MakeSoundVisitor implements AnimalVisitor {
    void visitDog(Dog dog) {
        // In a real case you'd obviously do something with the dog object
        System.out.println("bark! bark bark!!");
    }

    void visitCat(Cat cat) {
        System.out.println("meow meeeoooww!!");
    }
}

And then an usage of all of this would be:

var makeSoundVisitor = new MakeSoundVisitor();
var cat = new Cat();
var dog = new Dog();

cat.accept(makeSoundVisitor);
dog.accept(makeSoundVisitor);

But I really don't see the point of that accept call. If you've got the visitor and the objects to be visited, why not just pass these objects directly to the visitor and avoid the indirection? You could even get rid of the accept method on the Animal interface. Something like this:

var makeSoundVisitor = new MakeSoundVisitor();
var cat = new Cat();
var dog = new Dog();

makeSoundVisitor.visitCat(cat);
makeSoundVisitor.visitDog(dog);

Sources:

Best Answer

In your simple example, you know exactly the real type of the object on which you invoke the visitor and can therefore chose yourself the right visitor method:

makeSoundVisitor.visitCat(cat);      // You know that cat is a Cat
makeSoundVisitor.visitDog(dog);      // You know that dog is a Dog

But what if you don't know the type of the object? For example

Animal pet = getRandomAnimal();  

How would you now invoke your simplified visitor without the accept() method ? You'd probably need to find out the real type of pet first, and then call visitDog() or visitCat() with a downcast. This is all very cumbersome and error-prone.

With the classical visitor pattern, it's just the beauty of polymorphism that accept() allows:

pet.accept(makeSoundVisitor);

The underlying technique of double dispatch is worth to be known outside the visitor context.

Related Topic