做者:伯特
出處:github.com/ruicbAndroid/LoulanPlan
聲明:本文出自伯特的《 樓蘭計劃》,轉載務必註明做者及出處。
本文旨在討論抽象類和接口的做用、實例及使用場景,都是個人理解和總結。更多關於接口和抽象類的概念知識,可自行查閱相關文檔。java
抽象類,顧名思義,即類的抽象。git
在介紹面向對象概念時,咱們知道類是客觀事物的抽象,而抽象類又是類的進一步抽象,該怎麼理解呢?github
舉個例子,咱們定義若干個類 class BMW
、class Benz
、class Audi
,分別對客觀事物「寶馬」、「奔馳」、「奧迪」三種汽車進行抽象,包含相關屬性和行爲(即方法)。可是咱們知道,汽車都有通用的屬性和行爲,好比品牌、發動機、方向盤、輪胎等屬性,前進、後退、轉彎等行爲,因此咱們能夠在寶馬、奔馳等汽車之上,進一步抽象出「汽車」類 abstract class Car
,包含通用的特性(屬性和方法)。讓 BMW、Benz、Audi 等繼承抽象類 extends Car
,便擁有了汽車的通用特性,而後在抽象類基礎上定義各自的特殊屬性及方法。編程
這裏的 abstract class Car
即抽象類,能夠看出,抽象類是用來捕捉子類的通用特性的,包括屬性及行爲。設計模式
下面咱們來看看接口,假使我研發出來一臺會飛的汽車「伯特萊斯」(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
即爲接口,咱們一般使用接口對行爲進行抽象。
關於兩者的區別,能夠結合前面的例子,來加深理解。
抽象類是對類本質的抽象,表達的是 is a 的關係,好比:BMW
is a Car
。抽象類包含並實現子類的通用特性,將子類存在差別化的特性進行抽象,交由子類去實現。
而接口是對行爲的抽象,表達的是 like a 的關係。好比:Bote-Royce
like a Aircraft
(像飛行器同樣能夠飛),但其本質上 is a Car
。接口的核心是定義行爲,即實現類能夠作什麼,至於實現類主體是誰、是如何實現的,接口並不關心。
熟悉 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
能最大限度的減小對外暴露的接口,並隱藏細節,更符合這一原則。
因此說啊,面向對象只是指導咱們編程的思想,而非條條框框。在實際開發中,具體使用抽象類仍是接口,並無絕對限制,而是取決於你的業務場景和架構設計。
好了,本次關於接口與抽象類的總結就到這兒,你完全弄懂了嗎?下期分享再見~
歡迎關注個人公衆號「伯特說」: