深入理解Dart Mixin

假設(shè)我們需要實(shí)現(xiàn)一個(gè)動(dòng)物世界的功能。Animal作為基類(lèi)派生出哺乳類(lèi)、鳥(niǎo)類(lèi)、魚(yú)類(lèi)三種類(lèi)型,各個(gè)類(lèi)型又能派生出具體的動(dòng)物。每種動(dòng)物都具有步行、游泳、飛行三種能力中的某幾種能力:

1.png

由于Java和kotlin都不允許多繼承,我們可以將walk、swim、fly定義成interface,讓各個(gè)具體的動(dòng)物類(lèi)去實(shí)現(xiàn)這幾個(gè)接口。在java7里面需要在不同動(dòng)物類(lèi)中寫(xiě)同樣的實(shí)現(xiàn)代碼,但如果用java8或者kotlin,可以在interface中編寫(xiě)默認(rèn)實(shí)現(xiàn)去避免重復(fù)代碼。而在dart中我們要怎么實(shí)現(xiàn)呢?

dart extends & implements

首先dart里面是沒(méi)有interface的, 但是我們可以把class當(dāng)做接口被實(shí)現(xiàn)。使用implements把某個(gè)class當(dāng)做接口來(lái)實(shí)現(xiàn)要求我們重寫(xiě)這個(gè)class的所有方法, 而使用extends繼承某個(gè)class則可以繼承父類(lèi)實(shí)現(xiàn)了的方法:

class Base {
  void foo1() {
  }

  void foo2() {
  }
}

class Child1 implements Base {
  @override
  void foo1() {
    // TODO: implement foo1
  }

  @override
  void foo2() {
    // TODO: implement foo2
  }
}

class Child2 extends Base {
  
}

implements會(huì)將class的實(shí)現(xiàn)抹掉就不存在默認(rèn)實(shí)現(xiàn)一說(shuō),而dart也是不允許多繼承的。那么我們只能將walk、swim、fly三個(gè)接口在不同動(dòng)物類(lèi)中重復(fù)實(shí)現(xiàn)一遍嗎?

其實(shí)dart里有個(gè)叫做mixin的概念可以解決上面的問(wèn)題

mixin

mixin實(shí)際上也是面向?qū)ο缶幊讨械母拍?在維基百科上對(duì)它的解釋如下:

Mixin是面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言中的類(lèi),提供了方法的實(shí)現(xiàn)。其他類(lèi)可以訪(fǎng)問(wèn)mixin類(lèi)的方法而不必成為其子類(lèi)。[1]Mixin有時(shí)被稱(chēng)作"included"而不是"inherited"。mixin為使用它的class提供額外的功能,但自身卻不單獨(dú)使用(不能單獨(dú)生成實(shí)例對(duì)象,屬于抽象類(lèi))。因?yàn)橛幸陨舷拗疲琈ixin類(lèi)通常作為功能模塊使用,在需要該功能時(shí)“混入”,而且不會(huì)使類(lèi)的關(guān)系變得復(fù)雜。用戶(hù)與Mixin不是“is-a”的關(guān)系,而是“-able”關(guān)系

dart語(yǔ)言里面我們可以使用with關(guān)鍵字實(shí)現(xiàn)mixin,將一個(gè)或者多個(gè)class混入另一個(gè)類(lèi):

class Base1 {
  void foo1() {
    print("foo1");
  }
}

class Base2 {
  void foo2() {
    print("foo2");
  }
}

class Child2 with Base1, Base2 {

}

沒(méi)錯(cuò),通過(guò)with多個(gè)類(lèi),可以實(shí)現(xiàn)類(lèi)似多繼承的效果。

既然允許with多個(gè)類(lèi),那么如果這些類(lèi)中有個(gè)相同方法,那會(huì)出現(xiàn)什么事情。實(shí)際上kotlin、java8使用接口的默認(rèn)實(shí)現(xiàn)也會(huì)出現(xiàn)一樣的問(wèn)題,他們的處理方法是當(dāng)出現(xiàn)相同方法的時(shí)候?qū)崿F(xiàn)類(lèi)需要手動(dòng)指定使用哪個(gè)接口的默認(rèn)實(shí)現(xiàn),要不然編譯會(huì)報(bào)錯(cuò):

// java8
interface IBase1 {
    default void foo() {
        System.out.println("1");
    }
}

interface IBase2 {
    default void foo() {
        System.out.println("2");
    }
}

class Child implements IBase1, IBase2 {

    @Override
    public void foo() {
        IBase2.super.foo();
    }
}
//kotlin
interface IBase1 {
    fun foo() {
        println("1")
    }
}

interface IBase2 {
    fun foo() {
        println("2")
    }
}

class Child : IBase1, IBase2 {
    override fun foo() {
        super<IBase2>.foo()
    }
}

線(xiàn)性化

而在dart with里面越后面的類(lèi)優(yōu)先級(jí)越高:

class Base1 {
  void foo() {
    print("1");
  }
}

class Base2 {
  void foo() {
    print("2");
  }
}

class Base3 {
  void foo() {
    print("3");
  }
}

class Child extends Base1 with Base2, Base3 {}

這個(gè)時(shí)候調(diào)用Child.foo方法實(shí)際會(huì)優(yōu)先調(diào)用Base3.foo。原因是dart實(shí)際是通過(guò)創(chuàng)建中間類(lèi)繼承實(shí)現(xiàn)的mixin,上面的代碼相當(dāng)于:

2.png

通過(guò)從左到右的順序生成中間父類(lèi)去繼承將extends、with線(xiàn)性化成一個(gè)單繼承鏈。所以Base2、Base3實(shí)際上不是Child的父類(lèi)

mixin關(guān)鍵字

在上面的例子中我們使用普通的class去with,但dart實(shí)際上提供了一個(gè)mixin關(guān)鍵字,它定義了不能實(shí)例化,也不能extends只能with的類(lèi):

mixin Base {

}

// 編譯失敗: mixin類(lèi)不能extends
// class Child extends Base {
//
// }

// 編譯成功: mixin類(lèi)可以with
class Child with Base {

}

void main() {
  // 編譯失敗: mixin類(lèi)不能實(shí)例化
  // Base()
}

這樣的類(lèi)實(shí)際上和java、kotlin里面的interface已經(jīng)很像了。

另外我們可以通過(guò)mixin ... on 限定某個(gè)類(lèi)只能由某些類(lèi)去with:

class Base1 {
  void foo() {
    print("1");
  }
}

class Base2 {
  void foo() {
    print("2");
  }
}

mixin Base3 on Base1 {
  void foo() {
    super.foo();
    print("3");
  }
}

class Child extends Base1 with Base2, Base3 {}

上面的demo中Base3只能由Base1去with,那就以為著這個(gè)with Base3的類(lèi)一定是繼承或者with了 Base1,所以可以調(diào)用這個(gè)類(lèi)的super.foo方法。要注意的是,這個(gè)super.foo并不指定一定調(diào)用的是Base1.foo。例如上面的代碼調(diào)用Child().foo()之后的打印實(shí)際上是:

2
3

它們線(xiàn)性化的到的繼承關(guān)系和前面全是class的代碼并沒(méi)有差別:

3.png

從上面的uml圖我們就能理解為什么打印是23了

理解了這個(gè)簡(jiǎn)單的例子之后我們?cè)賮?lái)看一個(gè)復(fù)雜一點(diǎn)的例子:

class Base1 {
  void foo1() {
    print("Base1.foo1");
    foo2();
  }

  void foo2() {
    print("Base1.foo2");
  }
}

mixin Base2 on Base1 {
  void foo1() {
    super.foo1();
    print("Base2.foo1");
  }

  void foo2() {
    print("Base2.foo2");
  }
}

class Child with Base1,Base2 {}

void main() {
  Child().foo1();
}

它的輸出是:

Base1.foo1
Base2.foo2
Base2.foo1

原因是Base2.foo1中的super.foo1實(shí)際上調(diào)用的是Base1.foo1,而B(niǎo)ase1.foo1中的foo2,由于繼承的多態(tài)特性,調(diào)用的是Base2.foo2。我們可以通過(guò)下面uml圖輔助理解,注意看繼承關(guān)系里面是沒(méi)有Base1、Base2的因?yàn)樗鼈兌际峭ㄟ^(guò)with混入的,并不是Child的父類(lèi):

4.png

with的類(lèi)不能有構(gòu)造函數(shù)

另外,with的class和mixin類(lèi)型都是不允許有構(gòu)造函數(shù)的,因?yàn)閙ixin機(jī)制語(yǔ)義上是向一個(gè)類(lèi)混入其他類(lèi)的方法或者成員變量,使得我們可以在混合類(lèi)中訪(fǎng)問(wèn)到混入類(lèi)的方法或者屬性。而混入其他類(lèi)的構(gòu)造函數(shù)實(shí)際上是沒(méi)有意義的,因?yàn)椴粫?huì)有人手動(dòng)去調(diào)用這個(gè)混入類(lèi)的構(gòu)造函數(shù)。

class Base1 {
  Base1() {}
}

// 編譯失敗: 不能with一個(gè)帶有構(gòu)造函數(shù)的類(lèi)
// class Child with Base1 {}

// 編譯失敗: mixin類(lèi)型只能with,所以不能有構(gòu)造函數(shù)
// mixin Base2 {
//   Base2() {}
// }

參考博客

https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3

http://m.itdecent.cn/p/f4efaa6b8fe6

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在開(kāi)始閱讀此篇文章之前,我們可以先思考下如下問(wèn)題: 什么是 Mixin ? Mixin為什么會(huì)被設(shè)計(jì)出來(lái),它解決了...
    Joker_Wan閱讀 5,445評(píng)論 4 8
  • Flutter的動(dòng)畫(huà)體系是怎么運(yùn)作的,各組件之間的關(guān)聯(lián)關(guān)系及原理什么,隱式動(dòng)畫(huà)、顯式動(dòng)畫(huà)怎么區(qū)分,本文將會(huì)進(jìn)行詳細(xì)...
    whqfor閱讀 2,382評(píng)論 0 6
  • 一、前言 在使用Java語(yǔ)言設(shè)計(jì)類(lèi)之間關(guān)系的時(shí)候,我們會(huì)接觸到 組成單元 和 關(guān)系連接 這兩類(lèi)概念: 組成單元:普...
    澤毛閱讀 11,375評(píng)論 8 32
  • 會(huì)帶著以下幾個(gè)問(wèn)題來(lái)分享mixin的相關(guān)內(nèi)容 什么是mixin? 為什么需要mixin? 怎么樣使用? 什么場(chǎng)景下...
    flutter閱讀 1,230評(píng)論 0 0
  • HttpClient ?? HttpClient是Dart SDK中提供的標(biāo)準(zhǔn)的訪(fǎng)問(wèn)網(wǎng)絡(luò)的接口類(lèi),是HTTP1.1...
    TonyBuilder閱讀 4,249評(píng)論 3 4

友情鏈接更多精彩內(nèi)容