在 Dart 語言中,咱們常常能夠看到對 mixin
關鍵字的使用,根據字面理解,就是混合的意思。那麼,mixin
如何使用,它的使用場景是什麼呢。java
咱們假設一個需求,咱們須要用多個對象表示一些 動物, 諸如 狗、鳥、魚、青蛙。其中bash
基於以下一些考慮編輯器
咱們使用以下設計ide
代碼以下:函數
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
是如何線性化的
Dart 中的 mixin
經過建立一個類來實現,該類將 mixin
的實現層疊在一個超類之上以建立一個新類 ,它不是「在超類中」,而是在超類的「頂部」。
咱們能夠獲得如下幾個結論:
mixin
能夠實現相似多重繼承的功能,可是實際上和多重繼承又不同。多重繼承中相同的函數執行並不會存在 」父子「 關係mixin
能夠抽象和重用一系列特性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
呢?很簡單,在咱們編寫 Java 的時候,感受須要實現多個 interface
的時候。
那麼,這個和多重繼承相比,在某些場景有什麼好處嗎?答案是有。
在 Flutter 中,framework 的執行依賴多個 Binding
,咱們查看最外層 WidgetsFlutterBinding
的定義:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {}
複製代碼
在 WidgetsBinding
和 RendererBinding
中,都有一個叫作 drawFrame
的函數。在 WidgetsBinding
的 drawFrame
中,也有 super.drawFrame()
的調用。
這裏 mixin
的優勢就體現了出來,咱們能夠看到這個邏輯有以下2點
drawFrame
先於 render 層的調用,保證了 Flutter 在佈局和渲染處理中 widgets -> render 的處理順序具體的細節,感興趣的同窗能夠閱讀 Flutter 的 flutter package 的源碼。
這篇文,我對 Dart 的 mixin
的使用、工做機制、使用場景作了一個大體的總結。mixin
是一個強大的概念,咱們能夠跨越類的層次結構重用代碼。
文中一些優點和工做機制是個人我的理解。在初次接觸 Dart 的這個機制的時候,也須要不少的思惟轉變。若是文中我有理解的不對的地方,或者您有不一樣的理解。歡迎評論討論交流。