pjm56
pjm56

Reputation: 147

What is the correct pattern for processing a subclass in a type-specific way?

I have a collection of Animal objects.

My core code wants to treat all of these as Animals, all the same. Each Animal needs to be processed in some way. The nature of the processing depends on the sub-type of the Animal (bird, mammal, etc).

My code currently looks as follows.

public interface Animal {
    public String getTaxonomyClass(); 
}

public abstract class Bird implements Animal {

    @Override
    public String getTaxonomyClass() {
        return "aves";
    }

    // Specific to birds
    public abstract float getWingspan();

}

public abstract class Mammal implements Animal {

    @Override
    public String getTaxonomyClass() {
        return "mammalia";
    }

    // Specific to mammals
    public abstract int getToothCount();

}

public interface AnimalProcessor {
    public String getSupportedTaxonomyClass();
    public void process(Animal a);
}

public class MammalProcessor implements AnimalProcessor {

    @Override
    public String getSupportedTaxonomyClass() {
        return "mammalia";
    }

    @Override
    public void process(Animal a) {
        System.out.println("Tooth count is " + ((Mammal)a).getToothCount());
    }

}

public class BirdProcessor implements AnimalProcessor {

    @Override
    public String getSupportedTaxonomyClass() {
        return "aves";
    }

    @Override
    public void process(Animal a) {
        System.out.print("Wingspan is " + ((Bird)a).getWingspan());
    }

}

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ZooKeeper {

    Map<String, AnimalProcessor> registry = new HashMap<String, AnimalProcessor>();

    public void registerProcessor(AnimalProcessor ap)
    {
        registry.put(ap.getSupportedTaxonomyClass(), ap);
    }

    public void processNewAnimals(List<Animal> newcomers)
    {
        for(Animal critter : newcomers)
        {
            String taxonomy = critter.getTaxonomyClass();
            if(registry.containsKey(taxonomy))
            {
                // if I can process the animal, I will
                AnimalProcessor ap = registry.get(taxonomy);
                ap.process(critter);
            }

        }
    }
}

import java.util.LinkedList;
import java.util.List;

public class MainClass {

    public static void main(String[] args) {

        ZooKeeper keeper = new ZooKeeper();
        keeper.registerProcessor(new MammalProcessor());
        keeper.registerProcessor(new BirdProcessor());

        List<Animal> animals = new LinkedList<Animal>();

        animals.add(new Mammal() {  // badger

            @Override
            public int getToothCount() {
                return 40;
            } } 
        );

        animals.add(new Bird() {  // condor

            @Override
            public float getWingspan() {
                return 2.9f;
            } }
        );

        keeper.processNewAnimals(animals);

    }
}

Generally this is easy to understand and works nicely! I can add plug-in new processors and animal types at my leisure without changing the ZooKeeper class or any of the interfaces. You can imagine a more advanced main class, loading the Animals from a database, and processing them all in turn.

However, I worry about the downcasts inside the AnimalProcessor subclasses! This strikes me as something which should not be there, and may be a violation of OO principles. After all, at the moment I can pass a Bird to a MammalProcessor's process() method, and there will be a ClassCastException.

Can anyone suggest a design pattern to solve this? I looked at the Visitor pattern, but couldn't quite figure out how to apply it in this case! The key is to make the core code (ZooKeeper) treat all animals the same, and make it so that support for new Animals can be added trivially. Thanks!

Upvotes: 3

Views: 1283

Answers (6)

Qwerky
Qwerky

Reputation: 18445

Make you AnimalProcessor generic;

public interface AnimalProcessor<T extends Animal> {
    public String getSupportedTaxonomyClass();
    public void process(T a);
}

public class MammalProcessor implements AnimalProcessor<Mammal> {

    @Override
    public String getSupportedTaxonomyClass() {
        return "mammalia";
    }

    @Override
    public void process(Mammal a) {
        System.out.println("Tooth count is " + a.getToothCount());
    }

}

Upvotes: 1

Sorin
Sorin

Reputation: 1985

This is where generics work great.

First, you need to make AnimalProcessor generic:

public interface AnimalProcessor <T extends Animal> {
    public String getSupportedTaxonomyClass();
    public void process(T a);
}

Next, in your specific processors, you specify the generic type - eg for mammals:

public class MammalProcessor implements AnimalProcessor<Mammal> {

    public String getSupportedTaxonomyClass() {
        return "mammalia";
    }

    public void process(Mammal a) {
        System.out.println("Tooth count is " + a.getToothCount());
    }

}

Now, the process method only accepts Mammal objects, no birds here.

Upvotes: 2

Tony Ennis
Tony Ennis

Reputation: 12299

Your problem are methods such as

   public abstract int getToothCount();

...aren't defined in Animal. Instead, they are defined in specific subclasses of Animal. This means you can't treat Animals generically since they are fundamentally different.

To overcome this, the one approach would be to create abstract methods for all these in the Animal class.

Bird might respond to getToothCount() with "0".

Since all animals could respond to getWingspan(), getTootCount(), etc, you would not have to perform any type-specific checking. If this isn't good enough, create abstract implementations of "boolean hasWings()", "boolean hasTeeth()" etc etc in Animal.

Now you could say, for some animal a:

if (a.hasWings()) System.out.println("My wingspan is "+a.getWingSpan());

which would work for any animal. Of course, each subclass of Animal would have to implement all the various methods.

Another option is to add non-abstract methods to Animal. These methods would supply default answers. For example, getWingSpan() would return 0, getToothCount() would return 0, etc. Shark would override getToothCount(). Eagle would override getWingSpan()...

Then your subclasses would only have to override (or even know about) methods related directly to them.

Upvotes: 0

shem
shem

Reputation: 4712

I would suggest the following:

public interface Animal {
    public AnimalProcessor<? extends Animal> getProcessor();
}

so each animal will return it's matching processor.

public interface AnimalProcessor<T extends Animal> {
     public void process(T a);
}

so the processors will be typed with their matching type it's should process. so the implantation will be like this:

public abstract class Bird implements Animal {
    private BirdProcessor processor = new BirdProcessor();
    public abstract float getWingspan();
    @Override
    public AnimalProcessor<Bird> getProcessor() {
        return processor; 
    }
}

public class BirdProcessor implements AnimalProcessor<Bird> {
    @Override
    public void process(Bird b) {
        System.out.print("Wingspan is " + b.getWingspan());
    }
}

Upvotes: 3

wattostudios
wattostudios

Reputation: 8764

So you've got a class like this...

public abstract class Bird implements Animal {

    @Override
    public String getTaxonomyClass() {
        return "aves";
    }

    // Specific to birds
    public abstract float getWingspan();

}

All Birds will have a wingspan, even if the wingspan is 0. So, why don't you change the class to something like this...

public class Bird implements Animal {

    float wingspan = 0.0f;

    public Bird(float wingspan){
        this.wingspan = wingspan;
    }

    @Override
    public String getTaxonomyClass() {
        return "aves";
    }

    // Specific to birds
    public float getWingspan(){
        return wingspan;
    }

}

So, to create a new Bird, instead of doing this...

    animals.add(new Bird() {  // condor

        @Override
        public float getWingspan() {
            return 2.9f;
        } }
    );

You would just do this...

animals.add(new Bird(2.9f)); // condor

This would seem to make the whole thing a lot simpler and nicer for your purposes. You would do a similar change for your Mammal class too.

Now, for the processing of animals... if all Animals are going to be processed, you could just implement process() in Bird rather than needing a separate BirdProcessor class. To do this, in Animal, declare a method public void process();. Your Bird would implement it like this...

public void process() {
     System.out.print("Wingspan is " + getWingspan());
}

and you would change your AnimalProcessor to simply do this (note: no longer an interface)...

public class AnimalProcessor {
    public void process(Animal a) {
        a.process();
    }
}

Your AnimalProcessor class would then be able to handle all Animals.

Alternatively, if you want to leave AnimalProcessor as it is, it would probably be good to change the following though, to avoid the ClassCastException (this code here is for the BirdProcessor)...

public void process(Animal a) {
    if (a instanceof Bird){
        System.out.print("Wingspan is " + ((Bird)a).getWingspan());
    }
}

Is this kinda what you were looking for?

Upvotes: 0

giorashc
giorashc

Reputation: 13713

I suggest the following :

public interface Animal {
    public String getTaxonomyClass(); 
    public void process();
}

Now each animal class implementing Animal should implement its own processing logic. For example :

public class Bird implements Animal {

    public Bird(float wingSpan) {
        this.wingSpan = wingSpan;
    }

    @Override
    public String getTaxonomyClass() {
        return "aves";
    }

    @Override
    public void process() {
         System.out.print("Wingspan is " + wingSpan);
    }

    // Specific to birds
    private float wingspan;
}

Now you can have only one AnimalProcessor which processes as follows :

 public void process(Animal a) {
      a.process();
 }

Upvotes: 1

Related Questions