user3369398
user3369398

Reputation: 247

Java visitor pattern

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

Answers (2)

ECDragon
ECDragon

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

Andy Turner
Andy Turner

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

Related Questions