views:

27

answers:

2

I always tend to run into the following design problem that I'm never quite sure how to best resolve. It usually starts with a hierarchy of Animals at my Circus:

Animal
  Cat
    BigCat
  Dog          
  Elephant
  ...

Now, each Animal needs to be trained, so that there is a separate method for each:

public interface Trainer {
    void train( BigCat animal );
    void train( Dog animal );
    void train( Elephant animal );
    // ...
}

The problem is that the CircusDirector doesn't give a damn. He just throws Animals to the Trainer without even looking.

public class CircusDirector {
    public void work() {
        Trainer trainer = getTrainer();
        Animal animal = getAnimal();

        // ...and he doesn't know a frog from a pony,
        // so he tries to just:
        trainer.train(animal);
    }
}

Now, the Trainer can get an additional method like

void train( Animal animal );

where he'll use instanceof to send the animal to the appropriate method, but this seems ugly and doesn't come recommended. Is there a better solution using generics?

+2  A: 

What you're describing looks like it could be solved quite neatly with the Visitor pattern or, a little more precisely, double-dispatch.

Have your animals implement a Trainable interface:

interface Trainable {

    accept(Trainer trainer);

}

with an implementation like:

public Dog extends Animal implements Trainable {

  //... other dog stuff

  public accept(Trainer trainer) {
     trainer.train(this);
  }

}

And then keep the trainer the same way. All the animals will get dispatched appropriately.

GaryF
+2  A: 

You've essentially implemented half of the visitor pattern. You can have each of your Animal's provide an acceptTrainer(Trainer t) method which then call t.train(this);

Your Director would then call animal.acceptTrainer(trainer);

Also, I think generics info gets compiled out, so you can't do any fancy stuff relying on them. It's best to get familiar with some of the patterns.

SB
Does this mean that each subclass has to keep re-implementing the acceptTrainer() method?
Jake
@Jake: Sadly, yes, but as boilerplate goes, it's fairly minimal.
GaryF
@Jake: The animal-specific methods cannot be part of the Trainer -- that breaks basic encapsulation. The Animal-specific training methods must be part of the Animal, not the Trainer. So each Animal must provide Training-specific methods. acceptTrainer is one way to shape the API between Director, Trainer and each Animal instance.
S.Lott