首頁 > 軟體

Flutter 語法進階抽象類和介面本質區別詳解

2022-08-16 14:03:06

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 語法抽象類介面的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com