Reputation: 247
I try to use visitor pattern with extends classes. I have list of Animal classes each is different animal. When I call the visitor it'll execute only the talk(Animal a) and not the concrete instance of the object. See below:
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class Poodle extends Dog {}
class Visitor {
public void talk(Animal a) { System.out.println("?"); }
public void talk(Cat a) { System.out.println("Meow"); }
public void talk(Dog a) { System.out.println("bark"); }
public void talk(Poodle a) { System.out.println("Arf"); }
}
public class Demo{
public static void main(String []args){
Visitor visitor = new Visitor();
Animal list[] = { new Cat(), new Dog(), new Poodle() };
for (Animal a : list)
visitor.talk(a);
}
}
Output is:
?
?
?
While I expect:
Meow
bark
Arf
Any idea how to implement the visitor without putting many instanceof inside a single talk() method?
Upvotes: 1
Views: 396
Reputation: 512
The visitor pattern is basically a many-to-many class behavior resolution mechanism. In order to be useful / applicable to your animals example, we need to add a visitor, as Andy Turner indicated.
How about including "Animal Trainers" as the visitors? Different animal trainers could get the different animals to speak differently (or not).
So, first, the entity (animal interface). I like to think of this as the "Thing" being "acted on by an Actor":
public interface IAnimal {
public String getAnimalName();
// aka... public void accept(Visitor v)
public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer);
}
Then let's define a visitor type (animal trainer interface). I like to think of the visitor as an "Actor" acting on the various "Things" (entities):
public interface IAnimalTrainer {
public String getTrainerName();
//aka... public void visit(Dog dog);
public void animalTrainerMakesAnimalSpeak(Dog dog);
public void animalTrainerMakesAnimalSpeak(Cat cat);
public void animalTrainerMakesAnimalSpeak(Poodle poodle);
}
Now lets create the animals (implement IAnimal interface):
Cat:
public class Cat implements IAnimal {
private String animalName;
@Override
public String getAnimalName() {
return animalName;
}
public void setAnimalName(String animalName) {
this.animalName = animalName;
}
public Cat(String animalName) {
this.animalName = animalName;
}
public Cat() {
// Default constructor
}
@Override
public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
animalTrainer.animalTrainerMakesAnimalSpeak(this);
}
}
Dog:
public class Dog implements IAnimal {
private String animalName;
@Override
public String getAnimalName() {
return animalName;
}
public void setAnimalName(String animalName) {
this.animalName = animalName;
}
public Dog(String animalName) {
this.animalName = animalName;
}
public Dog() {
// Default constructor
}
@Override
public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
animalTrainer.animalTrainerMakesAnimalSpeak(this);
}
}
Poodle:
public class Poodle extends Dog {
public Poodle(String animalName) {
super(animalName);
}
public Poodle() {
super();
}
@Override
public void allowAnimalTrainerToMakeMeSpeak(IAnimalTrainer animalTrainer) {
animalTrainer.animalTrainerMakesAnimalSpeak(this);
}
}
Now lets create the animal trainers, Phil and Jack (implement IAnimalTrainer interface):
Animal Trainer Phil:
public class AnimalTrainerPhil implements IAnimalTrainer {
private String trainerName = "Phil";
@Override
public String getTrainerName() {
return trainerName;
}
public void setTrainerName(String trainerName) {
this.trainerName = trainerName;
}
@Override
public void animalTrainerMakesAnimalSpeak(Dog dog) {
System.out.println(
"Animal trainer "
+ getTrainerName()
+ " gets "
+ dog.getAnimalName()
+ " the dog to say: BARK!!");
}
@Override
public void animalTrainerMakesAnimalSpeak(Cat cat) {
System.out.println(
"Animal trainer "
+ getTrainerName()
+ " gets "
+ cat.getAnimalName()
+ " the cat to say: MEOW!!");
}
@Override
public void animalTrainerMakesAnimalSpeak(Poodle poodle) {
animalTrainerMakesAnimalSpeak((Dog)poodle);
}
}
Animal Trainer Jack:
public class AnimalTrainerJack implements IAnimalTrainer {
private String trainerName = "Jack";
@Override
public String getTrainerName() {
return trainerName;
}
public void setTrainerName(String trainerName) {
this.trainerName = trainerName;
}
@Override
public void animalTrainerMakesAnimalSpeak(Dog dog) {
System.out.println(
"Animal trainer "
+ getTrainerName()
+ " gets "
+ dog.getAnimalName()
+ " the dog to say: Bark bark.");
}
@Override
public void animalTrainerMakesAnimalSpeak(Cat cat) {
System.out.println(
"Animal trainer "
+ getTrainerName()
+ " gets "
+ cat.getAnimalName()
+ " the cat to say: Meoooow.");
}
@Override
public void animalTrainerMakesAnimalSpeak(Poodle poodle) {
System.out.println(
"Animal trainer "
+ getTrainerName()
+ " gets "
+ poodle.getAnimalName()
+ " the poodle to say: Yip! Yip!");
}
}
Now lets pull this all together and get all the animal trainers (Phil and Jack) to get all the animals (Cat, Dog, Poodle) to speak, via a Manager class:
public class ManagerOfAnimalTrainersAndAnimals {
public static void main(String[] args) {
ArrayList<IAnimal> allAnimals = new ArrayList<>();
allAnimals.add(new Dog("Henry"));
allAnimals.add(new Cat("Priscilla"));
allAnimals.add(new Poodle("Spike"));
ArrayList<IAnimalTrainer> allAnimalTrainers = new ArrayList<>();
allAnimalTrainers.add(new AnimalTrainerPhil());
allAnimalTrainers.add(new AnimalTrainerJack());
// Allow all animal trainers to get each animal to speak
for (IAnimalTrainer animalTrainer : allAnimalTrainers) {
for (IAnimal animal : allAnimals) {
animal.allowAnimalTrainerToMakeMeSpeak(animalTrainer);
}
}
}
}
Here's the output:
Animal trainer Phil gets Henry the dog to say: BARK!!
Animal trainer Phil gets Priscilla the cat to say: MEOW!!
Animal trainer Phil gets Spike the dog to say: BARK!!
Animal trainer Jack gets Henry the dog to say: Bark bark.
Animal trainer Jack gets Priscilla the cat to say: Meoooow.
Animal trainer Jack gets Spike the poodle to say: Yip! Yip!
This makes it relatively easy to add new trainers who have access to animal-type specific things (like if you added claw info to cats and paw info to dogs, ...) as they act on the various animals. I think the problem with the traditional "visitor" example is it's too nebulous and not concrete enough. The taxi example didn't do it for me, either. I hope this example helps.
Upvotes: 0
Reputation: 140544
A key element of the Visitor pattern is double-dispatch: you need to invoke the visitor method from the actual subclass:
abstract class Animal {
abstract void accept(Visitor v);
}
class Dog extends Animal {
@Override void accept(Visitor v) { v.talk(this); }
}
// ... etc for other animals.
Then, you pass the visitor to the animal, not the animal to the visitor:
for (Animal a : list)
a.accept(visitor);
The reason for this is that the compiler chooses the overload on Visitor
to invoke - it is not selected at runtime. All the compiler knows is that a
is an Animal
, so the only method it knows it is safe to invoke is the Visitor.visit(Animal)
overload.
Upvotes: 6