以前小鞏總是搞不懂Java中介面和抽象類別的區別,該用介面的用抽象類別,該用抽象類別的用介面,趁此機會,小鞏也重新對介面和抽象類別進行了學習。
從前面對物件導向的設計原則的講解,讀者可以瞭解到,其實所有的設計原則和設計模式都離不開抽象,因為只有抽象才能實現上述設計原則和設計模式。
在Java中,針對抽象有兩種實現方式,一種是介面,一種是抽象類別,有很多讀者也因此對這兩種實現方式比較困惑,到底是使用介面,還是使用抽象類別呢?對於它們的選擇甚至反映出對於問題領域本質的理解、對於設計意圖的理解是否正確、合理。
在物件導向的設計思想中,所有的對象都是通過類來描繪的,但是反過來,並不是所有的類都是用來描繪對象的,如果一個類中沒有描繪一個具體的對象,那麼這樣的類就是抽象類別,抽象類別是對那些看上去不同、但是本質上相同的具體概念的抽象,正是因為抽象的概念在問題領域沒有對應的具體概念,所以抽象類別是不能夠執行個體化的。
1.基本文法區別
在Java中,介面和抽象類別的定義文法是不一樣的,這裡以動物類為例來說明,其中定義介面的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public interface Animal {
/*所有動物都會吃*/
public void eat();
/*所有動物都會飛*/
public void fly();
}
定義抽象類別的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public abstract class Animal {
/*所有動物都會吃*/
public abstract void eat();
/*所有動物都會飛*/
public void fly() {
……
}
}
可以看到,在介面內只能是功能的定義,而抽象類別中則可以包括功能的定義和功能的實現,因此在介面中,所有的屬性肯定是public static final,所有的方法都是abstract,所以可以預設不寫上述標識符;而在抽象類別中,則即可以包含抽象的定義,也可以包含具體的實現方法。
在具體的實作類別上,介面和抽象類別的實作類別定義方式也是不一樣的,其中介面實作類別的示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal implements Animal {
/*所有動物都會吃*/
public void eat() {
……
}
/*所有動物都會飛*/
public void fly() {
……
}
}
抽象類別的實作類別示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal extends Animal {
/*所有動物都會吃*/
public void eat() {
……
}
/*所有動物都會飛*/
public void fly() {
……
}
}
可以看到,在介面的實作類別中,使用implements標識符,而在抽象類別的實作類別中,則使用extends標識符。一個介面的實作類別可以實現多個介面,而一個抽象類別的實作類別則只能實現一個抽象類別。
2.設計思想區別
從前面抽象類別的具體實作類別的實現方式可以看出,其實在Java中,抽象類別和具體實作類別之間是一種繼承關係,也就是說如果採用抽象類別的方式,則父類和子類在概念上應該是相同的,但介面卻不一樣,如果採用介面的方式,則父類和子類在概念上不要求相同,介面只是抽取相互之間沒有關係的類的共同特徵,而不去關注類之間的關係,它可以使沒有層次關係的類具有相同的行為。因此,可以這樣說:抽象類別是對一組具有相同屬性和方法的邏輯上有關係的事物的一種抽象,而介面則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象。
仍然以前面動物類的設計為例,來說明介面和抽象類別關於設計思想的區別,該動物類預設所有的動物都具有吃的功能,其中定義介面的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public interface Animal {
/*所有動物都會吃*/
public void eat();
}
定義抽象類別的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public abstract class Animal {
/*所有動物都會吃*/
public abstract void eat();
}
不管是實現介面,還是繼承抽象類別的具體動物,都具有吃的功能,具體的動物類的示意代碼如下。
介面實作類別的示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal implements Animal {
/*所有動物都會吃*/
public void eat() {
……
}
}
抽象類別的實作類別示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal extends Animal {
/*所有動物都會吃*/
public void eat() {
……
}
}
當然,具體的動物類不光具有吃的功能,比如有些動物還會飛,而有些動物卻會遊泳,那麼該如何設計這個抽象的動物類呢?可以分別在介面和抽象類別中增加飛的功能,其中定義介面的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public interface Animal {
/*所有動物都會吃*/
public void eat();
/*所有動物都會飛*/
public void fly();
}
定義抽象類別的示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public abstract class Animal {
/*所有動物都會吃*/
public abstract void eat();
/*所有動物都會飛*/
public abstract void fly();
}
但這樣一來,不管是介面還是抽象類別的實作類別,將都具有飛的功能,這顯然不能滿足要求,因為只有一部分動物會飛,而會飛的卻不一定是動物,比如飛機也會飛。那該如何設計呢?有很多種方案,比如再設計一個動物的介面類,該介面具有飛的功能,示意代碼如下:
//******* AnimalFly.java**************
package com.gongdan;
public interface AnimalFly {
/*動物會飛*/
public void fly();
}
然後那些具體的動物類,如果有飛的功能的話,除了實現吃的介面外,再實現飛的介面,示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal implements Animal, AnimalFly {
/*所有動物都會吃*/
public void eat() {
……
}
/*動物會飛*/
public void fly() {
……
}
}
而那些不需要飛的功能的具體動物類只實現具體吃的功能的介面即可。另外一種解決方案是:再設計一個動物的抽象類別,該抽象類別具有飛的功能,示意代碼如下:
//******* AnimalFly.java**************
package com.gongdan;
public abstract class AnimalFly {
/*動物會飛*/
public abstract void fly();
}
但此時就沒有辦法實現那些既有吃的功能又有飛的功能的具體動物類,因為在Java中具體的實作類別只能實現一個抽象類別。一個折中的解決辦法是,讓這個具有飛的功能的抽象類別,繼承具有吃的功能的抽象類別,示意代碼如下:
//******* AnimalFly.java**************
package com.gongdan;
public abstract class AnimalFly extends Animal {
/*動物會飛*/
public abstract void fly();
}
此時,對於那些只需要吃的功能的具體動物類來說,繼承Animal抽象類別即可,對於那些既有吃的功能又有飛的功能的具體動物類來說,則需要繼承AnimalFly抽象類別。
但此時對於用戶端有一個問題,那就是不能針對所有的動物類都使用Animal抽象類別來進行編程了,因為Animal抽象類別不具有飛的功能,這不符合物件導向的設計原則,因此這種解決方案其實是行不通的。
還有另外一種解決方案,即具有吃的功能的抽象動物類用抽象類別來實現,而具有飛的功能的類用介面實現,或者具有吃的功能的抽象動物類用介面來實現,而具有飛的功能的類用抽象類別實現。具有吃的功能的抽象動物類用抽象類別來實現,示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public abstract class Animal {
/*所有動物都會吃*/
public abstract void eat();
}
具有飛的功能的類用介面實現,示意代碼如下:
//******* AnimalFly.java**************
package com.gongdan;
public interface AnimalFly {
/*動物會飛*/
public void fly();
}
然後既具有吃的功能又具有飛的功能的具體的動物類,則繼承Animal動物抽象類別,實現AnimalFly介面,示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal extends Animal implements AnimalFly {
/*所有動物都會吃*/
public void eat() {
……
}
/*動物會飛*/
public void fly() {
……
}
}
或者具有吃的功能的抽象動物類用介面來實現,示意代碼如下:
//******* Animal.java**************
package com.gongdan;
public interface Animal {
/*所有動物都會吃*/
public void eat();
}
具有飛的功能的類用抽象類別實現,示意代碼如下:
//******* AnimalFly.java**************
package com.gongdan;
public abstract AnimalFly {
/*動物會飛*/
public abstract void fly();
}
然後既具有吃的功能又具有飛的功能的具體的動物類,則實現Animal動物類介面,繼承AnimalFly抽象類別,示意代碼如下:
//******* concreteAnimal.java**************
package com.gongdan;
public class concreteAnimal extends AnimalFly implements Animal {
/*所有動物都會吃*/
public void eat() {
……
}
/*動物會飛*/
public void fly() {
……
}
}
這些解決方案有什麼不同呢?再回過頭來看介面和抽象類別的區別:抽象類別是對一組具有相同屬性和方法的邏輯上有關係的事物的一種抽象,而介面則是對一組具有相同屬性和方法的邏輯上不相關的事物的一種抽象,因此抽象類別表示的是“is a”關係,介面表示的是“like a”關係。
假設現在要研究的系統只是動物系統,如果設計人員認為對於既具有吃的功能又具有飛的功能的具體的動物類來說,它和只具有吃的功能的動物一樣,都是動物,是一組邏輯上有關係的事物,因此這裡應該使用抽象類別來抽象具有吃的功能的動物類,即繼承Animal動物抽象類別,實現AnimalFly介面。
如果設計人員認為對於既具有吃的功能,又具有飛的功能的具體的動物類來說,它和只具有飛的功能的動物一樣,都是動物,是一組邏輯上有關係的事物,因此這裡應該使用抽象類別來抽象具有飛的功能的動物類,即實現Animal動物類介面,繼承AnimalFly抽象類別。
假設現在要研究的系統不只是動物系統,如果設計人員認為不管是吃的功能,還是飛的功能和動物類沒有什麼關係,因為飛機也會飛,人也會吃,則這裡應該實現兩個介面來分別抽象吃的功能和飛的功能,即實現吃的Animal介面外,再實現飛的AnimalFly介面。
從上面的分析可以看出:對於介面和抽象類別的選擇,反映出設計人員看待問題的不同角度,即抽象類別用於一組相關的事物,表示的是“is a”的關係;而介面用於一組不相關的事物,表示的是“like a”的關係。