I'm struggling with how I might avoid using instanceof() in some of my code. This contrived example somewhat captures the problem.
Class Meat extends Food;
Class Plant extends Food;
Class Animal;
Class Herbivore extends Animal
{
void eat( Plant food);
}
Class Carnivore extends Animal
{
void eat( Meat food);
}
Class Omnivore extends Animal
{
void eat(Food food);
}
Class Zoo
{
List<Animals> animals;
void receiveFood( Food food)
{
// only feed Plants to Herbivores and Meat to Carnivores
// feed either to Omnivores
}
}
Herbivores are only interested in Plants, Carnivores only in Meat and Omnivores both. When the Zoo receives Food it only makes sense to try to feed food to animals that eat that type of food.
I've thought of a few solutions, but all seem to depend on the use of instanceof()
somewhere and my various refactorings just seem to move it around.
(1) I could implement eat( Food food)
in Animal and each subclass could choose to ignore food that it doesn't eat, but that is inefficient and would require that each Animal subclass use instanceof()
to test the type of food.
(2) I could keep three collections of animals in the Zoo based on the type of food they eat, but would still have to use instanceOf()
to test the type of food to see which collection to feed it to. At least this would be more efficient as I wouldn't be feeding food to Animals that won't eat it.
I've thought of some other approaches, but again, they just seem to pass the instanceof()
buck.
Any suggestions? Or would this (2, at least) be an acceptable use of instanceof()
?
N
times but asking permission 2*N
times. This is very likely a reasonable inefficiency. If you are feeding VERY often, and the configurations of animals RARELY changes, it may be worth pre-processing them into lists of herbavores, omnivores, and carnivores - idbentley 2012-04-04 20:29
The visitor pattern solves your problem. Here's the code:
public abstract class Animal {
public abstract void accept(AnimalVisitor visitor);
}
public interface AnimalVisitor {
public void visit(Omnivore omnivore);
public void visit(Herbivore herbivore);
public void visit(Carnivore carnivore);
}
public class Carnivore extends Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
public void eat(Meat meat) {
System.out.println("Carnivore eating Meat...");
}
}
public class Herbivore extends Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
public void eat(Plant plant) {
System.out.println("Herbivore eating Plant...");
}
}
public class Omnivore extends Animal {
@Override
public void accept(AnimalVisitor visitor) {
visitor.visit(this);
}
public void eat(Food food) {
System.out.println("Omnivore eating " + food.getClass().getSimpleName() + "...");
}
}
public abstract class Food implements AnimalVisitor {
public void visit(Omnivore omnivore) {
omnivore.eat(this);
}
}
public class Meat extends Food {
@Override
public void visit(Carnivore carnivore) {
carnivore.eat(this);
}
@Override
public void visit(Herbivore herbivore) {
// do nothing
}
}
public class Plant extends Food {
@Override
public void visit(Carnivore carnivore) {
// do nothing
}
@Override
public void visit(Herbivore herbivore) {
herbivore.eat(this);
}
}
public class Zoo {
private List<Animal> animals = new ArrayList<Animal>();
public void addAnimal(Animal animal) {
animals.add(animal);
}
public void receiveFood(Food food) {
for (Animal animal : animals) {
animal.accept(food);
}
}
public static void main(String[] args) {
Zoo zoo = new Zoo();
zoo.addAnimal(new Herbivore());
zoo.addAnimal(new Carnivore());
zoo.addAnimal(new Omnivore());
zoo.receiveFood(new Plant());
zoo.receiveFood(new Meat());
}
}
Running the Zoo
demo prints
Herbivore eating Plant...
Omnivore eating Plant...
Carnivore eating Meat...
Omnivore eating Meat...
In your case, if the consumer of the object must know certain things about that object (e.g. is it meat), include a property in your base class isMeat()
and have concrete subclasses override the implementation of the base class method to return an appropriate value.
Leave that knowledge in the class itself, rather than in consumers of the class.
instanceof
is that in the case of the latter knowledge about the (possible) implementation is being transferred to the client class? I guess then it would come down to weighing this knowledge transfer to the client with cluttering of the base object with isXXX
methods. I can see different cases perhaps tipping the scale in either direction - HolySamosa 2012-04-11 16:51
A simple solution is when using multiple custom classes interacting with one another, just create isFood(), isAnimal(), isCarnivore(), etc. methods that return a boolean depending on which class they're in. It's not the prettiest but it gets the job done 100% of the time.
Expanding on my comment, I would attempt to use generics to help me here:
interface Animal<T extends Food> {
void eat(T food);
}
class Herbivore extends Animal<Plant> {
void eat(Plant food) { foo(); }
}
class Carnivore extends Animal<Meat> {
void eat(Meat food) { bar(); }
}
Note that this still doesn't solve the problem of iterating through a list of Food
and Animal
and only sending appropriate food to each animal -- I don't see a way to do that without explicit instanceof
style checks. But, it does allow you to be more specific with what your subclasses accept.
This question was mentioned above, but it does discuss some of the potential pitfalls of just such a use of generics. For the record, though, I had previously been considering just such a solution - HolySamosa 2012-04-05 16:09
Another solution is to maintain 2 lists: one for Herbivores and one for Carnivores.