這次,徹底弄懂介面及抽象類

伯特發表於2018-12-10

本文出自伯特的《LoulanPlan》,轉載務必註明作者及出處。

本文旨在討論抽象類和介面的作用、例項及使用場景,都是我的理解和總結。更多關於介面和抽象類的概念知識,可自行查閱相關文件。

1. 抽象類及其作用

抽象類,顧名思義,即類的抽象。

在介紹物件導向概念時,我們知道類是客觀事物的抽象,而抽象類又是類的進一步抽象,該怎麼理解呢?

舉個例子,我們定義若干個類 class BMWclass Benzclass 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. 總結

好了,本次關於介面與抽象類的總結就到這兒,你徹底弄懂了嗎?下期分享再見~

相關文章