Is there some other way I have not considered to fix it?
Double dispatch is one way to address this sort of problem. This is a way to apply inversion of control to get your polymorphic objects to trigger behavior appropriate to their classes in other objects. It is also known as the Visitor pattern. As applied to the problem posed, it might look like this:
public interface FooVisitor {
default void visit(Foo foo) { /* empty */ }
default void visit(Bar bar) { /* empty */ }
default void visit(Baz baz) { /* empty */ }
}
public class Foo {
void accept(FooVisitor v) {
v.visit(this);
}
}
public class Bar extends Foo {
void accept(FooVisitor v) {
v.visit(this);
}
}
public class Baz extends Foo {
void accept(FooVisitor v) {
v.visit(this);
}
}
public class StuffDoer implements FooVisitor {
public void visit(Bar bar) {
// do Bar stuff ...
}
public void visit(Baz baz) {
// do Baz stuff ...
}
}
class Manager {
public void doStuff(Foo foo) {
StuffDoer doer = new StuffDoer();
foo.accept(doer);
}
}
Note that although they are lexically identical, the three accept()
methods are semantically different because each one will be bound (at compile time) to a different overload of FooVisitor.visit()
. This is in fact the key to the pattern.
It may seem a bit wordy, but remember that you can define as many FooVisitor
implementations as you like for tasks that need to distinguish among your polymorphic objects by their actual class.