Reputation: 247
Here is a followup question to the one I already asked with better code example:
The following code use visitor pattern:
class Animal { void accept(Visitor v) { v.visit(this); } }
class Cat extends Animal {}
class Dog extends Animal {}
class Poodle extends Dog {}
interface Visitor {
public void visit(Animal a);
public void visit(Cat a);
public void visit(Dog a);
public void visit(Poodle a);
}
class TalkVisitor implements Visitor {
public void visit(Animal a) { System.out.println("?"); }
public void visit(Cat a) { System.out.println("Meow"); }
public void visit(Dog a) { System.out.println("bark"); }
public void visit(Poodle a) { System.out.println("Arf"); }
}
class WalkVisitor implements Visitor {
public void visit(Animal a) { System.out.println("?"); }
public void visit(Cat a) { System.out.println("Sneak"); }
public void visit(Dog a) { System.out.println("Walk"); }
public void visit(Poodle a) { System.out.println("Skip"); }
}
public class Demo{
public static void main(String []args){
Animal list[] = { new Cat(), new Dog(), new Poodle() };
for (Animal a : list)
a.accept(new TalkVisitor());
for (Animal a : list)
a.accept(new WalkVisitor());
}
}
The output is:
?
?
?
?
How can I fix it without adding switch of instanceof inside Animal.accept()? (I don't want to maintain switch() each time I add a new animal class)
Upvotes: 2
Views: 473
Reputation: 42541
I think it doesn't make sense to implement a visit method for the abstract class (Animal in your case).
In Visitor you always know all the possible subtypes. Its a kind of basic assumption (otherwise you add new methods to the Visitor interface). But you gain the ability to dynamically implement different behaviors. In your case its Talking and Walking.
The price to pay is to implement an "accept" method in every concrete type. You've tried to provide a more general solution and got confused :) For example take a look at Wikipedia description.
They are talking about different parts of Car, but the idea is the same: they implement an accept method for all the parts.
Upvotes: 6
Reputation: 197
As mentioned in previous answer, the accept
method must be abstract and implemented in all concrete subtypes.
Now they are two additional problems with your implementation:
Dog
class is not abstract. meaning that if you provide a default implementation of the accept method in Dog
, you lose the major reason to use the visitor pattern: you cannot be sure that you have correctly handled all sub-types of Dog.Visitor
methods "return" void
: this force all your instance of Visitor
to work via side-effect. You may find the corrected visitor pattern much less error-prone.Then, the visitor pattern is very verbose, so if you want to really deruce the your maintenance workload, please consider using a code generator (annotation processor). I can recommend two of them: adt4j (jdk7+) and derive4j (jdk8+). Those generators generates the correct sub-classes and implementation of the accept method. For your exemple, with derive4j and applying the two "fix" above, you would write:
import static Animals.*;
import static Dogs.*;
import java.util.Arrays;
import java.util.function.Function;
class Cat {}
@org.derive4j.Data
abstract class Dog {
interface Visitor<R> {
R Terrier(String name);
R Poodle(String name);
}
abstract <R> R accept(Visitor<R> visitor);
}
@org.derive4j.Data
abstract class Animal {
interface Visitor<R> {
R cat(Cat cat);
R dog(Dog dog);
}
abstract <R> R accept(Visitor<R> visitor);
public static void main(String[] args) {
final Function<Animal, String> talkVisitor = Animals.cases()
.cat("Meow")
.dog(Dogs.cases()
.Poodle("Arf")
.otherwise("bark"));
final Function<Animal, String> walkVisitor = Animals.cases()
.cat("Sneak")
.dog(Dogs.cases()
.Poodle("Skip")
.otherwise("Walk"));
Animal[] list = { cat(new Cat()), dog(Terrier("Max")), dog(Poodle("Minus")) };
for (Animal a : list)
System.out.println(talkVisitor.apply(a));
for (Animal a : list)
System.out.println(walkVisitor.apply(a));
// or better:
Arrays.asList(list).stream()
.map(talkVisitor)
.forEach(System.out::println);
}
}
Upvotes: 0