Flutter 中不得不會的 mixin

mixin 是 Dart 中非常重要的概念,對於未接觸過此概念的Coder來說尤其重要,最近看源碼的時候,由於對 mixin 不熟悉導致理解出現偏差,走了很多彎路,所以這篇文章介紹一下 mixin 概念。

Dart 及 Engine 版本:

Engine • revision ae90085a84
Tools • Dart 2.10.4

請注意版本,不同的版本可能存在差異。

先來看下官方的定義:

Mixins are a way of reusing a class』s code in multiple class hierarchies.

Mixins 是一種在多個類層次結構中重用類程式碼的方法。

在來看下 Wiki 的解釋:

In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin’s methods depends on the language. Mixins are sometimes described as being “included” rather than “inherited”.

Mixins encourage code reuse and can be used to avoid the inheritance ambiguity that multiple inheritance can cause (the “diamond problem”), or to work around lack of support for multiple inheritance in a language. A mixin can also be viewed as an interface with implemented methods. This pattern is an example of enforcing the dependency inversion principle.

翻譯如下:

在面向對象的程式語言中,mixin(或mix-in)是一個類,其中包含供其他類使用的方法,而不必成為其他類的父類。 這些其他類如何獲得對mixin方法的訪問許可權取決於語言。 混合素有時被描述為「包含」而不是「繼承」。

Mixins鼓勵程式碼重用,並且可用於避免多重繼承可能導致的繼承歧義(「鑽石問題」),或解決語言中對多重繼承的支援不足的問題。 混合也可以看作是已實現方法的介面。 此模式是強制執行依賴關係反轉原理的示例。

看完這兩段介紹,可能依然對其比較模糊,不要緊,現在只需對其有個概念即可,下面會詳細介紹 Mixins 的用法,我個人的理解就是:Mixins 解決了無法多重繼承的問題。

什麼時候需要使用 Mixins

有如下場景:

定義一個基類人(Person),它有吃(eat)的方法。

有3個實際的人A、B、C,它們都繼承 Person,但是3個人有不同的技能:

  • A :會唱歌、跳舞
  • B:會跳舞、寫程式碼
  • C:會唱歌、寫程式碼

上面的場景中唱歌、跳舞、寫程式碼是一種技能,並不是每一個人都會的,所以將其定義在 Person 中是不合適的,如果各自定義為一個類,又不能同時繼承Person和唱歌、跳舞、寫程式碼,如果將唱歌、跳舞、寫程式碼定義為 Interface ,那麼A、B、C中要各自實現其方法,

那要如何實現呢? Mixins 出場啦。

定義一個 Person 基類和功能類唱歌、跳舞、寫程式碼:

class Person {
  eat() {
    print('Person eat');
  }
}

class Dance {
  dance() {
    print('Dance dance');
  }
}

class Sing {
  sing() {
    print('Sing sing');
  }
}

class Code {
  code() {
    print('Code code');
  }
}

定義A、B、C:

class A extends Person with Dance, Sing {}

class B extends Person with Sing, Code {}

class C extends Person with Code, Dance {}

注意:混合使用 with 關鍵字。

使用:

A a = A();
a.eat();
a.dance();
a.sing();

輸出日誌:

flutter: Person eat
flutter: Dance dance
flutter: Sing sing

可以看到 A 中有了Dance 和Sing的相關的方法。

Dance 是一個 class,如果給其添加構造函數會如何?

給 Dance 添加構造函數,修改如下,

此時發現 A 和 C 無法編譯,出現如下錯誤:

很明顯,需要 mixin 的類無法定義構造函數。

所以一般會將需要 mixin 的類使用 mixin 關鍵字:

添加限定條件,使用關鍵字 on

接著上面的場景繼續,這時定義一個狗的類,目前狗這個類也可以混合 Dance 、Sing 和 Code,

class Dog with Code{}

但是,Code 是人類獨有的技能,不希望 Dog 這個類可以mixin,所以給 Code 添加限定條件:

使用關鍵字 on 限定Code 只能被 Person 或者其子類 mixin。

此時 Dog 無法 mixin Code。

添加限定後,可以重寫其方法, Code 重寫 Person 的方法:

super 表示調用父類(Person)的方法。

如何處理多個類有同一方法的情況

假設有D 和 D1 兩個類,有同一個方法 d,E mixin D 和 D1:

此時,調用 e.d 方法:

E e = E();
e.d();

輸出:

flutter: D1 d

說明後面的將前面的覆蓋了,調換下D 和 D1的順序:

class E with D1, D {}

輸出:

flutter: D d

此時在 E 中也添加 d 方法:

輸出:

flutter: E d

說明 E 中 方法覆蓋了原來的。

E 中 d 方法可以調用 super.d()

輸出:

flutter: D d
flutter: E d

假設現在有F、G、H 三個類,都有 a 方法,

有如下定義的類:

那麼下面會輸出什麼值:

答案是:

flutter: G a

記住:混合類時,進行混合的多個類是線性的,這是他們共有方法不衝突的原因,混合的順序非常重要,因為它決定了混合時相同的方法的處理邏輯。

再次看下 FG 的混合情況:

FG 繼承 H,混合 F 和 G,對於相同方法的優先順序為:G > F > H,因此共有方法 a,最後執行的是 G 類中的 a 方法。

那麼如果 FG 中也有 a 方法會如何?

如果本身(FG)也存在相同的方法那麼優先順序:FG > G > F > H。super.a() 執行的是 G 中的 a 方法。

輸出結果:

flutter: G a
flutter: FG a

更複雜的來啦,請看如下混合關係:

BB 為一個抽象類,有一個構造函數,其中執行 init 方法,GB 和 PB 為一個混合類型,限定了只有 BB 或者其子類才能混合,WFB 繼承 BB,並混合GB、PB,此時創建 WFB 對象,

WFB wfb = WFB();

輸出結果是什麼?

flutter: BB Constructor
flutter: BB init
flutter: GB init
flutter: PB init

是不是很詫異,按照上面的邏輯不是應該只調用 PB 的 init 方法嗎?

你理解的沒有錯,的確只調用了PB 的 init 方法,但是 PB 的 init 方法中調用了super.init(),這個才是重點,PB 通過 super.init 調用到了GB中的 init 方法, GB 通過 super.init 調用到了 BB 中的 init 方法,所以最終輸出的就是上面的結果。

這個一定要理解其中的調用順序,因為的 Flutter Framework 的入口函數 runApp 中就是此形式:

WidgetsFlutterBinding.ensureInitialized 方法如下:

WidgetsFlutterBinding 混合結構如下:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

BindingBase 及構造函數如下:

其執行了 initInstances 和 initServiceExtensions 方法。看下面混合的順序:

從後到前依次執行其 initInstances 和 initServiceExtensions(如果有) 方法,由於 initInstances 和 initServiceExtensions 方法中首先執行 super.initInstances()super.initServiceExtensions() ,所以最後執行的順序為:BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBindinsg -> WidgetsBinding 。

類型

還是上面的F、G、H 三個類,那麼 FG 的類型是什麼,看下面的判斷會輸出什麼?

輸出:

flutter: FG is F : true
flutter: FG is G : true
flutter: FG is H : true

所以混合後的類型是超類的子類型。

總結

  1. Mixins 使我們可以在無需繼承父類的情況下為此類添加父類的「功能」,可以在同一個類中具有一個父級和多個 mixin 組件。
  2. Mixins 不可以聲明任何構造函數。
  3. Mixins 添加限定條件使用 on 關鍵字。
  4. 混合使用 with 關鍵字,with 後面可以是 classabstract classmixin 的類型。
  5. Mixins 不是多重繼承,相反,它只是在多個層次結構中重用類中的程式碼而無需擴展它們的一種方式。

交流

老孟Flutter部落格(330個控制項用法+實戰入門系列文章)://laomengit.com

添加微信或者公眾號領取 《330個控制項大全》和 《Flutter 實戰》PDF。

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

Tags: