Reputation: 147
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
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
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
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
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
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
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