一篇帶你入門面向介面程式設計

dudu19939發表於2020-11-30

文章的開頭我們先來複習一下物件導向程式設計的幾大特性:封裝、繼承、多型、抽象。而面向介面程式設計是物件導向程式設計的一個重要方面。它是上面提到的物件導向的四大特性和一些設計模式的基礎。然而並不是所有的程式語言都支援介面這一特性。在不同的語言中對於抽象類和介面的支援程度也各有不同。本文會首先介紹介面的使用意義,然後介紹介面與抽象類的區別和聯絡,最後再介紹C++和JAVA兩大主流語言中對於介面和抽象類的支援情況。

1.介面的意義

很多同學在一開始學習介面的時候並不明白為什麼要有介面,教科書上的語言過於抽象以至於讓人費解,案例又過於短小以至於不能體現介面的意義。我在最開始學習介面的時候總是不知道如何使用它(當時沒有接觸過正經的專案),甚至於一度覺得不用介面不也可以實現功能嗎?那麼介面的價值體現在哪裡呢?

本文重點介紹介面的兩大價值:1.將協議標準與行為解耦,對呼叫者而言可以遮蔽底層的實現。2.定義好一種標準,使框架/系統更加易於擴充套件。第二點可以理解為第一點的補充。

舉一個生活中最常用的關於介面的例子:USB介面。有人說你怎麼舉例的是一種硬體的介面呀?其實硬體的介面和我們程式碼裡面的介面的含義是相似的,完全可以藉助硬體上我們熟悉的介面來理解物件導向程式設計中的介面。

百度百科上對於USB是這樣定義的:“USB,是英文Universal Serial Bus(通用序列匯流排)的縮寫,是一個外部匯流排標準,用於規範電腦與外部裝置的連線和通訊。“這裡我們可以看到,USB首先是一套標準。也就是說,介面它是定義了一種標準。甭管你是U盤還是硬碟,是機械硬碟還是SSD,只要您想通過USB與電腦通訊,您都要遵守並實現一套USB協議。那對於電腦(呼叫者)來說,電腦不用關注U盤讀取資料和硬碟讀取資料的具體區別,它只需要按照USB介面這套標準來呼叫資料的提供者進行讀取資料即可。這樣就把協議標準與行為進行了解耦,對呼叫者而言可以遮蔽底層的實現。

回到軟體開發上,我們提供介面供別人呼叫的時候,也是起到了解耦的作用。呼叫者無需關注實現的演算法和資料結構是什麼,只要你知道我是符合這個介面的,介面裡面定義的功能我都有就行了。所以我們更傾向於介面是表達了一種has-a的關係。這裡再舉一個java開發裡面常見的例子:

我們都知道List 是一個介面,它繼承於Collection的介面。List介面的實現有很多,包括:ArrayList,LinkedList,Vector,Stack等等。假如ArrayList和LinkedList不使用面向介面程式設計,即它們沒有都去實現List介面。那麼我們使用的時候就會這樣(用兩個方法分別處理ArrayList和LinkedList):

deal(ArrayList arrayList) {
arrayList.add();
// 此處省略若干對於list的呼叫
}

deal(LinkedList linkedList) {
linkedList.add();
// 此處省略若干對於list的呼叫
}

但是幸運的是,jdk的程式碼是面向介面的,ArrayList和LinkedList都實現了List介面,那麼我們只需要真的List這個型別定義一個方法即可處理所有實現了List介面的型別:

deal(List list) {
list.add();
// 此處省略若干對於list的呼叫
}

顯然在後者中,我們作為呼叫者無需關注ArrayList 和LinkedList 的區別了,只需要知道它們都實現了LIst介面就行。

我們再來看介面的第二點意義:定義好標準,提高系統的擴充套件性。

我們以Spring框架中InitializingBean介面為例。這個介面是Spring框架為它的使用者提供的一個擴充套件點,如果使用Spring框架進行開發的開發者,希望在初始化Bean的時候可以實現自定義的功能,那麼就應該實現這個介面。換言之,InitializingBean介面為bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是繼承該介面的類,在初始化bean的時候都會執行該方法。

好的框架需要給使用者預留恰到好處的擴充套件功能,支援使用者進行一些自定義的設定。但是前提是框架必須定義好這個擴充套件點(往往是用介面定義的,因為擴充套件必須遵守一定的標準)。這就好像一個U盤插到USB介面之後再開機,那麼機器是可以識別到這個U盤的。因為機器開機的時候就會去檢查這些介面,然後載入。但是如果機器預留的是一個USB介面結果使用者把一個TYPE C介面接入到了USB介面上(不進行任何適配和轉換的情況下),那麼肯定是識別不了的。簡單說就是我作為框架允許你擴充套件,但是你的擴充套件必須遵守我定義好的標準。

2.介面與抽象類有哪些區別和聯絡?

乍一看介面和抽象類的功能都是抽象,好像兩者差不多,然而實際上並非如此。

我們先來看抽象類和介面的作用,本質上就是有所不同的。

抽象類是一種類,本質上是為了程式碼複用(牛逼的用法是利用多型的程式碼複用)。抽象類表達的是繼承關係,是is-a的關係。而介面是為了解耦,表達的是一種契約/約定/協議/標準,是has-a 的關係。

他倆的功能和意義就是完全不同的。瞭解了兩者的根本不同之後,我們再來比較其他的特點。

首先,抽象類和介面都不可以被直接例項化。

其次,抽象類中允許有成員變數,介面中不允許,介面中只允許存在靜態變數。

再次,抽象類中允許有包含程式碼實現的方法,但是介面中不允許。介面中所有的方法必須都是抽象的。

最後,抽象類的實現類可以只實現抽象類中的抽象方法,但介面的實現類需要實現介面的所有方法(java8中允許在介面中使用default關鍵字定義方法,這種方法可以不在實現類中實現)。

3.C++和JAVA兩大主流語言中對於介面和抽象類的支援

JAVA支援介面(Interface關鍵字)和抽象類(abstract關鍵字),但是C++中,介面就是一種特殊的抽象類。雖然程式語言對於介面和抽象類的支援程度不盡相同,但是作為開發者仍然可以也應該有面向介面程式設計的思想。

關於介面和抽象類的基本知識就先聊到這裡,歡迎你在評論區留言你的意見和建議!後續將會推出文章談一下java底層是如何支援多型這一特性的,如果感興趣請關注我的公眾號!

相關文章