Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Questions about the Visitor pattern (sample in Java)

I'm just trying to understand the main benefits of using the Visitor pattern.

Here's a sample Java implementation

///////////////////////////////////
// Interfaces
interface MamalVisitor {
    void visit(Mammal mammal);
}
interface MammalVisitable {
    public void accept(MamalVisitor visitor);
}
interface Mammal extends MammalVisitable {
    public int getLegsNumber();
}
///////////////////////////////////


///////////////////////////////////
// Model
class Human implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 2; }
}
//PIRATE HAS A WOOD LEG
class Pirate extends Human { 
    @Override
    public int getLegsNumber() { return 1; }
    public int getWoodLegNumber() { return 1; }
}
class Dog implements Mammal {
    @Override
    public void accept(MamalVisitor visitor) {  visitor.visit(this);  }
    @Override
    public int getLegsNumber() { return 4; }
}
///////////////////////////////////


///////////////////////////////////
class LegCounterVisitor implements MamalVisitor {
    private int legNumber = 0;
    @Override
    public void visit(Mammal mammal) {   legNumber += mammal.getLegsNumber();   }
    public int getLegNumber() { return legNumber; }
}
class WoodLegCounterVisitor implements MamalVisitor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Mammal mammal) {   
        // perhaps bad but i'm lazy
        if ( mammal instanceof Pirate ) {
            woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
        }
    }
    public int getWoodLegNumber() { return woodLegNumber; }
}
///////////////////////////////////



///////////////////////////////////
public class Main {
    public static void main(String[] args) {
        // Create a list with 9 mammal legs and 3 pirate woodlegs
        List<Mammal> mammalList = Arrays.asList(
                new Pirate(),
                new Dog(),
                new Human(),
                new Pirate(),
                new Pirate()
        );

        ///////////////////////////////////
        // The visitor method
        LegCounterVisitor legCounterVisitor = new LegCounterVisitor();
        WoodLegCounterVisitor woodLegCounterVisitor = new WoodLegCounterVisitor();
        for ( Mammal mammal : mammalList ) {
            mammal.accept(legCounterVisitor);
            mammal.accept(woodLegCounterVisitor);
            // why not also using:
            // legCounterVisitor.visit(mammal);
            // woodLegCounterVisitor.visit(mammal);
        }
        System.out.println("Number of legs:" + legCounterVisitor.getLegNumber());
        System.out.println("Number of wood legs:" + woodLegCounterVisitor.getWoodLegNumber());

        ///////////////////////////////////
        // The standart method
        int legNumber = 0;
        int woodLegNumber = 0;
        for ( Mammal mammal : mammalList ) {
            legNumber += mammal.getLegsNumber();
            // perhaps bad but i'm lazy
            if ( mammal instanceof Pirate ) {
                woodLegNumber += ((Pirate) mammal).getWoodLegNumber();
            }
        }
        System.out.println("Number of legs:" + legNumber);
        System.out.println("Number of wood legs:" + woodLegNumber);
    }
}
///////////////////////////////////

I just wonder what is the main advantage for this case to use such a pattern. We can also iterate over the collection and get almost the same thing, except we don't have to handle a new interface and add boilerplate code to the model...

With Apache Commons, or a functional language, the classic way seems to do some map/reduce operation (map to the leg numbers and reduce with addition) and it's quite easy...

I also wonder why we use

        mammal.accept(legCounterVisitor);
        mammal.accept(woodLegCounterVisitor);

and not

        legCounterVisitor.visit(mammal);
        woodLegCounterVisitor.visit(mammal);

The 2nd option seems to remove the accept(...) method on the model part.

In many samples i've found, it seems that they don't use a common interface for model objects. I added it because like that i just have to add one visit(Mammal) method, instead of implementing one for each Mammal. Is it good to make all my objects implement Mammal? (i guess sometimes it's just not possible anyway). Is it still a Visitor pattern like that?

So my questions are: - do you see any advantage in my exemple for using visitors? - if not, can you provide some concrete usecases for visitors? - are visitors useful in functional programming languages

The only exemple that i found relevant for this pattern is the case of a pretty printer, where you keep in the visitor's state the offset to use during the visit of different nodes (for displaying an XML tree for exemple)

like image 549
Sebastien Lorber Avatar asked Jun 06 '11 12:06

Sebastien Lorber


2 Answers

The visitor pattern is just double dispatch.

I'm not sure I agree with your implementation of a visitor. I'd implement something like this:

interface MammalVisitor {
    void visit(Pirate pirate);
    void visit(Human human);
    void visit(Dog dog);
}

// Basic visitor provides no-op behaviour for everything.
abstract class MammalAdapter implements MammalVisitor {
    void visit(Pirate pirate) {};
    void visit(Human human) {};
    void visit(Dog dog) {};
}

And then the implementation would become cleaner:

// We only want to provide specific behaviour for pirates
class WoodLegCounterVisitor extends MammalAdaptor {
    private int woodLegNumber = 0;
    @Override
    public void visit(Pirate pirate) {   
        woodLegNumber += pirate.getWoodLegNumber();
    }

    public int getWoodLegNumber() { return woodLegNumber; }
}

In answer to your actual question, the main advantage of using the visitor is avoiding the need to do the "instanceof" checks. It gives you the ability to separate out the logic for processing a hierarchy into a separate class. It also gives you the ability to add new behaviour without changing the original classes.

like image 170
Jeff Foster Avatar answered Oct 18 '22 11:10

Jeff Foster


Visitor pattern is a fancy switch case / pattern matching system to facilitate graph traversal.

As typical functional languages offer pattern matching and efficient ways to traverse graphs, interest is much more limited.

Even in JAVA, with instanceof or using enum, a visitor is more of a fancy way to perform things than a generic solution as many algorithms will not fit well into it.

like image 38
Nicolas Bousquet Avatar answered Oct 18 '22 09:10

Nicolas Bousquet