Flutter 語法進階抽象類和接口本質區別詳解
1. 接口存在的意義?
在 Dart 中 接口 定義並沒有對應的關鍵字。可能有些人覺得 Dart 中弱化瞭 接口 的概念,其實不然。我們一般對接口的理解是:接口是更高級別的抽象,接口中的方法都是 抽象方法 ,沒有方法體。通過接口的定義,我們可以通過定義接口來聲明功能,通過實現接口來確保某類擁有這些功能。
不過你有沒有仔細想過,為什麼接口會存在,引入接口的概念是為瞭解決什麼問題?可能有人會說,通過接口,可以規范一類事物的功能,可以面向接口進行操作,從而可以更加靈活地進行拓展。其實這隻是接口的作用,而且這些功能 抽象類 也可以支持。所以接口一定存在什麼特殊的功能,是抽象類無法做到的。
都是抽象方法的抽象類,和接口有什麼本質的區別呢?在我的初入編程時,這個問題就伴隨著我,但漸漸地,這個問題好像對編程沒有什麼影響,也就被遺忘瞭。網上很多文章介紹 抽象類 和 接口 的區別,隻是在說些無關痛癢的形式區別,並不能讓我覺得接口存在有什麼必要性。
思考一件事物存在的本質意義,可以從沒有這個事物會產生什麼後果來分析。現在想一下,如果沒有接口,一切的抽象行為僅靠 抽象類 完成會有什麼局限性 或說 弊端。沒有接口,就沒有 實現 (implements) 的概念,其實這就等價於在問 implements 消失瞭,對編程有什麼影響。沒有實現,類之間就隻能通過 繼承 (extends) 來維護 is-a 的關系。所以就等價於在問 extends 有什麼局限性 或說 弊端。答案呼之欲出:多繼承的二義性 。
那問題來瞭,為什麼類不能支持 多繼承 ,而接口可以支持 多實現 ,繼承 和 實現 有什麼本質的區別呢?為什麼 實現 不會帶來 二義性 的問題,這是理解接口存在關鍵。
2. 繼承 VS 實現
下面我們來探討一下 繼承 和 實現 的本質區別。如下 A 和 B 類,有一個相同的成員變量和成員方法:
class A{ String name; A(this.name); void run(){ print("B"); } } class B{ String name; B(this.name); void run(){ print("B"); } }
對於繼承而言 派生類 會擁有基類的成員變量與成員方法,如果支持多繼承,就會出現兩個問題:
- 問題一 : 基類中有同名 成員變量 ,無法確定成員的歸屬類
- 問題二: 基類中有同名 成員方法 ,且子類未覆寫。在調用時,無法確定執行哪個。
class C extends A , B { C(String name) : super(name); // 如果多繼承,該為哪個基類的 name 成員賦值 ?? } void main(){ C c = C("hello") c.run(); // 如果多繼承,該執行哪個基類的 run 方法 ?? }
其實仔細思考一下,一般意義上的接口之所以能夠 多實現 ,就是通過限制,對這兩個問題進行解決。比如 Java 中:
- 不允許在接口中定義普通的 成員變量 ,解決問題一。
- 在接口中隻定義抽象成員方法,不進行實現。而是強制派生類進行實現,解決問題二。
abstract class A{ void run(); } abstract class B{ void run(); } class C implements A,B{ @override void run() { print("C"); } }
到這裡,我們就認識到瞭為什麼接口不存在 多實現 的二義性問題。這就是 繼承 和 實現 最本質的區別,也是 抽象類 和 接口 最重要的差異。從這裡可以看出,接口就是為瞭解決多繼承二義性的問題,而引入的概念,這就是它存在的意義。
3. Dart 中接口與實現的特殊性
Dart 中並不像 Java 那樣,有明確的關鍵字作為 接口類 的標識。因為 Dart 中的接口概念不再是 傳統意義 上的狹義接口。而是 Dart 中的任何類都可以作為接口,包括普通的類,這也是為什麼 Dart 不提供關鍵字來表示接口的原因。
既然普通類可以作為接口,那多實現中的 二義性問題 是必須要解決的,Dart 中是如何處理的呢? 如下是 A 、B 兩個普通類,其中有兩個同名 run 方法:
class A{ void run(){ print("run in a"); } } class B{ void run(){ print("run in a"); } void log(){ print("log in a"); } }
當 C 類實現 A 、B 接口,必須強制覆寫 所有 成員方法 ,這點解決瞭二義性的 問題二 :
那 問題一 中的 成員變量 的歧義如何解決呢?如下,在 A 、B 中添加同名的成員變量:
class A{ final String name; A(this.name); // 略同... } class B{ final String name; B(this.name); // 略同... }
當 C 類實現 A 、B 接口,必須強制覆為 所有 成員變量提供 get 方法 ,這點解決瞭二義性的 問題一 :
這樣,C 就可以實現兩個普通類,而避免瞭二義性問題:
class C implements A, B { @override String get name => "C"; @override void log() {} @override void run() {} }
其實,這是 Dart 對 implements 關鍵字的功能加強,迫使派生類必須提供 所有 成員變量的 get 方法,必須覆寫 所有 成員方法。這樣就可以讓 類 和 接口 成為兩個獨立的概念,一個 class 既可以是類,也可以是接口,具有雙重身份。
其區別在於,在 extend 關鍵字後,表示繼承,是作為類來對待;
在 implements 關鍵字之後,表示實現,是作為接口來對待。
4.Dart 中抽象類作為接口的小細節
我們知道,抽象類中允許定義 普通成員變量/方法 。下面舉個小例子說明一下 繼承 extend 和 實現 implements 的區別。對於繼承來說,派生類隻需要實現抽象方法即可,抽象基類 中的普通成員方法可以不覆寫:
而前面說過,implements 關鍵字要求派生類必須覆寫 接口 中的 所有 方法 。也就表示下面的 C implements A 時,也必須覆寫 log 方法。從這個例子中,可以很清楚地看出 繼承 和 實現 的差異性。
抽象類 和 接口 的區別,就是 繼承 和 實現 的區別,在代碼上的體現是 extend 和 implements 關鍵字功能的區別。隻有理解 繼承 的局限性,才能認清 接口 存在的必要性。
以上就是Flutter 語法進階抽象類和接口本質區別詳解的詳細內容,更多關於Flutter 語法抽象類接口的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Flutter路由fluro引入配置和使用的具體方法
- Java深入數據結構理解掌握抽象類與接口
- Java中比較抽象類與接口的異同
- Java抽象類和接口使用梳理
- Flutter 給列表增加下拉刷新和上滑加載更多功能