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