完全理解 Dart mixin 機制

理解 Dart mixin 機制

在 Dart 語言中,咱們常常能夠看到對 mixin 關鍵字的使用,根據字面理解,就是混合的意思。那麼,mixin 如何使用,它的使用場景是什麼呢。java

從一個實例提及

咱們假設一個需求,咱們須要用多個對象表示一些 動物, 諸如 狗、鳥、魚、青蛙。其中bash

  1. 狗會跑
  2. 鳥會飛
  3. 魚會游泳
  4. 青蛙是兩棲動物,會跑,而且會游泳

基於以下一些考慮編輯器

  • 動物特性可能會繼續增多,而且一個動物可能具有多種技能
  • 動物種類不少,可是能夠歸大類。例如 鳥禽、哺乳類

咱們使用以下設計ide

  • 動物繼承自 Animal 抽象類
  • 跑、飛、遊 抽象爲接口

代碼以下:函數

abstract class Animal {
}

class Run {
    run() {
        print('run');
    }
}

class Fly {
    fly() {
        print('fly');
    }
}

class Swim {
    swim(){
        print('swim');
    }
}

class Bird extends Animal implements Fly {
    @override
    fly() {
        super.fly();
    }
}

class Dog extends Animal implements Run {
    @override
    run() {
        super.run();
    }
}

class Fish extends Animal implements Swim {
    @override
    swim() {
        super.swim();
    }
}

class Frog extends Animal implements Run,Swim {
    @override
    run() {
        super.run();
    }

    @override
    swim() {
        super.swim();
    }
}
複製代碼

這個時候,咱們會發現編輯器報了個錯佈局

原來這個方法 Dart 會一直認爲 super 調用是在調用一個 abstract 的函數,因此咱們這時候須要把這裏面集成的函數實現一一實現。ui

這時候問題來了,Frog 和 Fish 都實現了 Swim 接口,這時候 swim 函數的內容咱們須要重複的寫 2 遍!spa

回想一下咱們當初在 Android 中寫 Java 或者 Kotlin 的時候,其實也有相似問題,同一個 interface 內的 method, 咱們可能須要重寫 n 次,很是明顯的代碼冗餘。設計

Java8 和 Kotlin 選擇使用接口的 default 實現來解決這個問題:3d

interface IXX {
    default void xmethod() {
        /// do sth...
    }
}
複製代碼

而 Dart, 選擇使用 mixin

修改上面的代碼:

abstract class Animal {
}

mixin Run {
    run() {
        print('run');
    }
}

mixin Fly {
    fly() {
        print('fly');
    }
}

mixin Swim {
    swim(){
        print('swim');
    }
}

class Bird extends Animal with Flym {}
class Dog extends Animal with Run {}
class Fish extends Animal with Swim {}
class Frog extends Animal with Run,Swim {}
複製代碼

咱們運行以下代碼

Bird bird = Bird();
bird.fly();

Frog frog = Frog();
frog.run();
frog.swim();
複製代碼

輸出以下:

fly
run
swim
複製代碼

這裏咱們能夠意識到,mixin 被混入到了具體的類中,實際也起到了實現具體特性的做用。可是相比實現接口來講,更加的便捷一點。

這裏類的繼承關係咱們能夠梳理成下圖

當函數同樣的時候

上述的例子結束了 mixin 的基本用法。咱們能夠看到每一個類均可以經過 with 關鍵字,把 mixin 中定義的特性 「混入」 到本身這裏來。可是這時候若是每一個 mixin 的函數名是同樣的,會發生什麼呢?咱們不妨從新寫一個簡單的例子。

class S {
  fun()=>print('A');
}
mixin MA {
  fun()=>print('MA');
}
mixin MB {
  fun()=>print('MB');
}
class A extends S with MA,MB {}
class B extends S with MB,MA {}
複製代碼

運行以下代碼

main() {
A a = A();
a.fun();
B b = B();
b.fun();
}
複製代碼

咱們獲得下面這個輸出

MB
MA
複製代碼

這個時候咱們會發現,最後混入的 mixin 的函數,被調用了。這說明最後一個混入的 mixins 會覆蓋前面一個 mixins 的特性。爲了驗證這個工做流程,咱們稍微修改一下這個例子,給 mixins 的函數加上 super 調用。

mixin MA on S {
  fun() {
    super.fun();
    print('MA');
  }
}
mixin MB on S {
  fun() {
    super.fun();
    print('MB');
  }
}
複製代碼

繼續執行上面的程序,輸出結果以下

A
MA
MB
A
MB
MA
複製代碼

第一個 A#fun 爲例子。咱們發現實際的調用順序爲 MB -> MA -> A,這裏咱們能夠看出來 mixin 的工做方式,是具備線性化的。

mixin的線性化

上面的示例,咱們能夠畫一個圖來表示 mixin 是如何線性化的

Dart 中的 mixin 經過建立一個類來實現,該類將 mixin 的實現層疊在一個超類之上以建立一個新類 ,它不是「在超類中」,而是在超類的「頂部」。

咱們能夠獲得如下幾個結論:

  1. mixin 能夠實現相似多重繼承的功能,可是實際上和多重繼承又不同。多重繼承中相同的函數執行並不會存在 」父子「 關係
  2. mixin 能夠抽象和重用一系列特性
  3. mixin 實際上實現了一條繼承鏈

最終咱們能夠得出一個很重要的結論

聲明 mixin 的順序表明了繼承鏈的繼承順序,聲明在後面的 mixin,通常會最早執行

這裏再提出一個假設,若是 MA 和 MB 都有一個函數叫 log, 若是在先聲明的 mixin 中執行 log 函數,會發生聲明事情呢?

代碼以下

mixin MA on S {
  fun() {
    super.fun();
    log();
    print('MA');
  }

  log() {
    print('log MA');
  }
}
mixin MB on S {
  fun() {
    super.fun();
    print('MB');
  }

  log() {
    print('log MB');
  }
}

class A extends S with MA,MB {}
A a = A();
a.fun();
複製代碼

這裏按照習慣性的思惟,咱們可能會獲得

A
log MA
MA
MB
複製代碼

的結果。實際上,咱們的輸出是

A
log MB
MA
MB
複製代碼

仔細思考一下,按照上面的工做原理,在 mixin 的繼承鏈創建的時候,最後聲明的 mixin 會把後聲明的 minxin 的函數覆蓋掉。這時候即便咱們從代碼邏輯中認爲在 MA 中調用了 log 函數,實際上這時候 A 類中的 log 函數已經被 MB 給覆蓋了。因此最終,log 函數調用的是 MB 中的 log 函數邏輯。

類型

根據 mixin 的工做原理,咱們徹底能夠大膽猜測,最終的子類類型和這個繼承鏈上全部父類和混入的 mixin 的類型均可以匹配上。咱們來驗證一下這個猜測:

A a = A();
print(a is A);
print(a is S);
print(a is MA);
print(a is MB);
複製代碼

輸出結果

true
true
true
true
複製代碼

推論徹底正確。

mixin 的使用場景

咱們應該在何時使用 mixin 呢?很簡單,在咱們編寫 Java 的時候,感受須要實現多個 interface 的時候。

那麼,這個和多重繼承相比,在某些場景有什麼好處嗎?答案是有。

在 Flutter 中,framework 的執行依賴多個 Binding,咱們查看最外層 WidgetsFlutterBinding 的定義:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {}
複製代碼

WidgetsBindingRendererBinding 中,都有一個叫作 drawFrame 的函數。在 WidgetsBindingdrawFrame 中,也有 super.drawFrame() 的調用。

這裏 mixin 的優勢就體現了出來,咱們能夠看到這個邏輯有以下2點

  1. 保證了 widget 等的 drawFrame 先於 render 層的調用,保證了 Flutter 在佈局和渲染處理中 widgets -> render 的處理順序
  2. 保證順序的同時,Widgets 和 Render 仍然屬於 2 個不一樣的對象定義,職責分割的很是的清晰。

具體的細節,感興趣的同窗能夠閱讀 Flutter 的 flutter package 的源碼。

小結

這篇文,我對 Dart 的 mixin 的使用、工做機制、使用場景作了一個大體的總結。mixin 是一個強大的概念,咱們能夠跨越類的層次結構重用代碼。

文中一些優點和工做機制是個人我的理解。在初次接觸 Dart 的這個機制的時候,也須要不少的思惟轉變。若是文中我有理解的不對的地方,或者您有不一樣的理解。歡迎評論討論交流。

相關文章
相關標籤/搜索