假设我们有以下玩具接口:
这个程序会按预期运行,因为传递给函数的对象恰好可以转换为
我经常看到像这样的Java代码,有时甚至出现在教科书中(例如O'Reilly的《Head First Design Patterns》第300页),所以我肯定没有领会其中的优点。
如果我要编写类似的代码,我会尽量避免向不能保证的类型或接口进行向下转型。例如,在这种情况下,我会这样做:
interface Speakable
{
public abstract void Speak();
}
interface Flyer
{
public abstract void Fly();
}
我们有一个实现了这两个接口的类:
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");
}
}
目前我看到有多种方法可以在Duck
上调用方法,但我无法决定哪种是最佳实践。考虑以下情况:
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);
}
}
这个程序会按预期运行,因为传递给函数的对象恰好可以转换为
Flyer
和Speakable
,但我看到这样的代码会感到不安,因为它不允许编译时类型检查,由于紧密耦合,当传递不同类型的对象(不能转换为任一接口)作为参数时,或者如果Duck
的实现在以后更改,不能再实现Flyer
时,它可能会抛出意外异常。我经常看到像这样的Java代码,有时甚至出现在教科书中(例如O'Reilly的《Head First Design Patterns》第300页),所以我肯定没有领会其中的优点。
如果我要编写类似的代码,我会尽量避免向不能保证的类型或接口进行向下转型。例如,在这种情况下,我会这样做:
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!");
}
}
这将使我能够:
private static void SafeSpeakAndFly(SpeakingFlyer x)
{
x.Speak();
x.Fly();
}
public static void main(String[] args)
{
BuzzLightyear bly= new BuzzLightyear();
SafeSpeakAndFly(bly);
}
这是否是一种不必要的过度设计?这样做有哪些缺陷?
我认为这种设计将SafeSpeakAndFly()
函数与其参数分离,并通过编译时类型检查可避免出现讨厌的错误。
为什么第一种方法在实践中被广泛使用,而后者则不是呢?