Suppose we have the following toy interfaces:
interface Speakable
{
public abstract void Speak();
}
interface Flyer
{
public abstract void Fly();
}
and we have a class that implements both interfaces:
class Duck implements Speakable, Flyer
{
public void Speak()
{
System.out.println("quack quack don't eat me I taste bad.");
}
public void Fly()
{
System.out.println("I am flying");
}
}
At this point I see different ways to invoke methods on Duck
and I can't decide which one is best practice.
Consider this scenario:
public class Lab
{
private static void DangerousSpeakAndFly(Object x)
{
Speakable temp = (Speakable) x;
temp.Speak();
Flyer temp2= (Flyer) x;
temp2.Fly();
}
public static void main(String[] args)
{
Duck daffy= new Duck();
DangerousSpeakAndFly(daffy);
}
}
This program will behave as expected, because the object passed in to the function happens to be castable to Flyer
and Speakable
, but I cringe when I see code like this because it does not allow compile time type checking and due to tight coupling it can throw unexpected exceptions for example when a differently typed object (not castable to either or one of the interfaces) is passed in as parameter, or if implementation of Duck
changes down the line so it no longer implements Flyer
.
I see Java code written like this all the time, sometimes in textbooks (for example pg. 300 of "Head First Design Patterns" by O'Reilly) so there must be a merit in it that I am missing.
If I were to write similar Code I would try to avoid downcasting to a type or interface that is not guaranteed. for example in this scenario I would do something like this:
interface SpeakingFlyer extends Flyer, Speakable
{
}
class BuzzLightyear implements SpeakingFlyer
{
public void Speak()
{
System.out.println("My name is Buzz");
}
public void Fly()
{
System.out.println("To infinity and beyond!");
}
}
Which would allow me to do:
private static void SafeSpeakAndFly(SpeakingFlyer x)
{
x.Speak();
x.Fly();
}
public static void main(String[] args)
{
BuzzLightyear bly= new BuzzLightyear();
SafeSpeakAndFly(bly);
}
Is this an unnecessary overkill? what are the pitfalls for doing this?
I feel like this design decouples the SafeSpeakAndFly()
function from its parameters and keeps nasty bugs at bay due to compile time type checking.
Why is the first method used so extensively in practice and the latter isn't?