此次,完全弄懂接口及抽象類

LoulanPlan

做者:伯特
出處:github.com/ruicbAndroid/LoulanPlan
聲明:本文出自伯特的《 樓蘭計劃》,轉載務必註明做者及出處。

本文旨在討論抽象類和接口的做用、實例及使用場景,都是個人理解和總結。更多關於接口和抽象類的概念知識,可自行查閱相關文檔。java

1. 抽象類及其做用

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

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

舉個例子,咱們定義若干個類 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("僞裝會飛~");
    }
}

看起來沒問題:ide

  • BoteRoyce extends Car:表達這是一輛汽車;
  • fly() 方法:體現這車能夠飛。

可是,隨着技術發展,出現了衆多能夠製造飛行汽車的廠商,難道每個能夠飛的汽車都去定義一個 fly() 方法?ui

心想這還不簡單,在抽象類 Car 中定義一個抽象方法 abstract void fly() 讓子類去實現,不就能夠了嗎?spa

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. 總結

好了,本次關於接口與抽象類的總結就到這兒,你完全弄懂了嗎?下期分享再見~

歡迎關注個人公衆號「伯特說」:
伯特說

相關文章
相關標籤/搜索