本文出自伯特的《LoulanPlan》,轉載務必註明作者及出處。
本文旨在討論抽象類和介面的作用、例項及使用場景,都是我的理解和總結。更多關於介面和抽象類的概念知識,可自行查閱相關文件。
1. 抽象類及其作用
抽象類,顧名思義,即類的抽象。
在介紹物件導向概念時,我們知道類是客觀事物的抽象,而抽象類又是類的進一步抽象,該怎麼理解呢?
舉個例子,我們定義若干個類 class BMW
、class Benz
、class Audi
,分別對客觀事物“寶馬”、“賓士”、“奧迪”三種汽車進行抽象,包含相關屬性和行為(即方法)。但是我們知道,汽車都有通用的屬性和行為,比如品牌、發動機、方向盤、輪胎等屬性,前進、後退、轉彎等行為,所以我們可以在寶馬、賓士等汽車之上,進一步抽象出“汽車”類 abstract class Car
,包含通用的特性(屬性和方法)。讓 BMW、Benz、Audi 等繼承抽象類 extends Car
,便擁有了汽車的通用特性,然後在抽象類基礎上定義各自的特殊屬性及方法。
這裡的 abstract class Car
即抽象類,可以看出,抽象類是用來捕捉子類的通用特性的,包括屬性及行為。
2. 介面及其作用
下面我們來看看介面,假使我研發出來一臺會飛的汽車“伯特萊斯”(Bote-Royce),在程式中定義如下:
class BoteRoyce extends Car {
//...省略通用特性
/**
* 可以飛
*/
void fly() {
System.out.println("假裝會飛~");
}
}
複製程式碼
看起來沒問題:
BoteRoyce extends Car
:表達這是一輛汽車;fly()
方法:體現這車可以飛。
但是,隨著技術發展,出現了眾多可以製造飛行汽車的廠商,難道每一個可以飛的汽車都去定義一個 fly()
方法?
心想這還不簡單,在抽象類 Car
中定義一個抽象方法 abstract void fly()
讓子類去實現,不就可以了嗎?
No No No... 正如不是所有牛奶都叫特侖蘇一樣,不是所有汽車都會飛,飛行功能不是汽車的通用特性。將 fly()
方法定義在 Car
中,顯然違背了“抽象類用來捕捉子類的通用特性”這一原則。
在這種場景下,解決方案之一就是使用介面,如下:
/**
* 飛行器介面
*/
public interface Aircraft {
//定義抽象方法
void fly();
}
複製程式碼
類 BoteRoyce
的定義修改如下:
/*
* 實現 Aircraft 介面,表示具備飛行器能力
*/
class BoteRoyce extends Car implements Aircraft {
/**
* 覆寫介面方法,實現飛行能力
*/
@Override
void fly() {
System.out.println("假裝會飛~");
}
}
複製程式碼
再有其他品牌的飛行汽車,都可以通過 extends Car implements Aircraft
實現飛行能力。
上述定義的 interface Aircraft
即為介面,我們通常使用介面對行為進行抽象。
3. 介面和抽象類的區別
關於二者的區別,可以結合前面的例子,來加深理解。
抽象類是對類本質的抽象,表達的是 is a 的關係,比如:BMW
is a Car
。抽象類包含並實現子類的通用特性,將子類存在差異化的特性進行抽象,交由子類去實現。
而介面是對行為的抽象,表達的是 like a 的關係。比如:Bote-Royce
like a Aircraft
(像飛行器一樣可以飛),但其本質上 is a Car
。介面的核心是定義行為,即實現類可以做什麼,至於實現類主體是誰、是如何實現的,介面並不關心。
4. 介面與抽象類的使用場景
熟悉 Java 的同學可能會質疑,上述關於介面的使用,完全可以通過再次抽象 Car
去實現:
/**
* 會飛的汽車
*/
abstract class FlyCar extends Car {
//定義抽象方法
public abstract void fly();
}
複製程式碼
普通的汽車依然 extends Car
,可以飛行的汽車 extends FlyCar
即可:
/*
* 繼承 FlyCar,表示是可以飛行的汽車
*/
class BoteRoyce extends FlyCar {
/**
* 覆寫抽象方法,實現飛行能力
*/
@Override
public void fly() {
System.out.println("假裝會飛~");
}
}
複製程式碼
如果你也這麼想,表示你 get 到了抽象類的點。不過話說回來,這樣的話介面豈不是沒有存在的意義了?
當然不是了。就 BoteRoyce
而言,如果你關心的是“飛行汽車”這個整體,那麼定義抽象類 FlyCar
是個不錯的選擇;如果你關心的是汽車具備“飛行”的行為,那不妨繼續沿用前面使用 Aircraft
介面的方案。
這一點與設計模式中六大原則之一的“里氏替換原則”不謀而合,該原則指出:所有引用基類(抽象類或介面)的地方必須能透明地使用其子類的物件。也就是說,當你遵循該原則時,你必須要考慮你關心的是“飛行汽車”實體,還是“飛行”行為,並將其作為基類,從而決定程式所能接受的子類物件。
同時,“介面隔離原則”指導我們,一個類對另一個類的依賴應該建立在最小的介面上。相比於抽象類 FlyCar
,介面 Aircraft
能最大限度的減少對外暴露的介面,並隱藏細節,更符合這一原則。
所以說啊,物件導向只是指導我們程式設計的思想,而非條條框框。在實際開發中,具體使用抽象類還是介面,並沒有絕對限制,而是取決於你的業務場景和架構設計。
5. 總結
好了,本次關於介面與抽象類的總結就到這兒,你徹底弄懂了嗎?下期分享再見~