1

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?

3
  • what description of the visitor pattern did you use? Commented Feb 5, 2016 at 9:25
  • If you would like to loop through the list as Animals and not override the functions, you should check with instanceof and then cast the object. For example inside your list if(a instanceof cat){ visitor.talk(((Cat)a).talk()}. But i HIGHLY recommend just Overriding the method, since you are already using inheretance Commented Feb 5, 2016 at 9:30
  • 1
    As a hater of the visitor pattern, I ask, why not just rely on a typical interface/class hierarchy instead of forcing a pattern on it? Commented Feb 5, 2016 at 9:41

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Ugh, my head hurts. Please tell me I'm not the only one that sees this as a misuse of the pattern. Your example is good as far as the implementation but isn't it muddied by the fact these relationships have no business visiting each other? Unlike with collisions, for example, where the types of the visitable and visitor involved matter, this example need not interact with the visitor to talk. The solution to OP's problem seems polymorphic, not pattern, based. +1 to show it's useful in this context.
You are correct that it is overkill for this simple example.
...but a simple example also makes it simple to show where it is wrong, so that it is easier to understand how to implement it correctly where it is appropriate.
Agreed; a simple example is indeed helpful. But I see many posters struggling to understand polymorphism, for example, in part/whole due to a poor use case. It's hard not to see this as an example that looks past the power of poly while at the same time makes visitor more difficult to appreciate. None of this your fault of course :)
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.