[英]Java best practice: casting objects vs interfaces
假設我們有以下玩具界面:
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()
函數與其參數分離,並且由於編譯時類型檢查而阻止了令人討厭的錯誤。
為什么第一種方法在實踐中如此廣泛地使用而后者不是?
我看到Java代碼一直都是這樣編寫的,有時候是在教科書中(例如O'Reilly的“Head First Design Patterns”第300頁)所以我必須要有一個我缺少的優點。
這本書最初發表於2004年,我認為Java當時並不支持Generics。 因此,不安全的鑄造是當時非常常用的。 可能,如果我沒有Java中參數多態的支持,我首先要檢查參數是否是我想要將其轉換為的類型的實例,然后執行實際的轉換:
private static void dangerousSpeakAndFly(Object x) {
if (x instanceof Speakable) {
Speakable temp = (Speakable) x;
temp.Speak();
}
if (x instanceof Flyer) {
Flyer temp2= (Flyer) x;
temp2.Fly();
}
}
但是,擁有泛型可以讓我們這樣做:
private static <T extends Speakable & Flyer> void reallySafeSpeakAndFly(T x) {
x.Speak();
x.Fly();
}
在這里,編譯器可以確保我們沒有傳遞一些沒有實現Speakable
和Flyer
東西,並且可以在編譯時檢測到這些Speakable
嘗試。
為什么第一種方法在實踐中如此廣泛地使用而后者不是?
我想,你可能已經看過很多遺留代碼了。 :)
您可以強制參數與Speakable
和Flyer
同時使用類型交集創建方法泛型:
private <T extends Speakable & Flyer> static void DangerousSpeakAndFly(T x) {
// use any of `Speakable` or `Flyer` methods of `x`
}
因此,您不需要轉換或創建其他接口。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.