You are absolutely right, this style of programming kills maintainability of your code right away.
There are two simple ways of handling this - implementing a visitor, and defining a Map
based on Class<T>
.
Here is an example of the first approach:
interface Visitor {
void visitGiraffe(Giraffe g);
void visitZebra(Zebra z);
}
abstract class Animal {
public abstract void accept(Visitor v);
}
class Giraffe extends Animal {
public void accept(Visitor v) {
v.visitGiraffe(this);
}
}
class Zebra extends Animal {
public void accept(Visitor v) {
v.visitZebra(this);
}
}
With this structure in hand, you can write your remover as follows:
void removeFromWorld(Animal a) {
a.accept(new Visitor() {
public void visitGiraffe(Giraffe g) {
setOfGiraffes.remove(g);
}
public void visitZebra(Zebra z) {
setOfZebras.remove(z);
}
});
}
The second relies on the ability of Java objects to produce their Class
. Now instead of defining
Set<Giraffe> setOfGiraffes = ...
Set<Zebra> setOfZebras = ...
you can define
Map<Class,Set<Animal>> setOfAnimalByClass = ...
To access giraffes, you would do
setOfAnimalByClass.get(Giraffe.class).add(new Giraffe());
and so on. Then you can implement removeFromWorld
like this:
void removeFromWorld(Animal a) {
a.accept(new Visitor() {
setOfAnimals.get(a.getClass()).remove(a);
});
}