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

由于Java和kotlin都不允許多繼承,我們可以將walk、swim、fly定義成interface,讓各個具體的動物類去實現(xiàn)這幾個接口。在java7里面需要在不同動物類中寫同樣的實現(xiàn)代碼,但如果用java8或者kotlin,可以在interface中編寫默認實現(xiàn)去避免重復代碼。而在dart中我們要怎么實現(xiàn)呢?
dart extends & implements
首先dart里面是沒有interface的, 但是我們可以把class當做接口被實現(xiàn)。使用implements把某個class當做接口來實現(xiàn)要求我們重寫這個class的所有方法, 而使用extends繼承某個class則可以繼承父類實現(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會將class的實現(xiàn)抹掉就不存在默認實現(xiàn)一說,而dart也是不允許多繼承的。那么我們只能將walk、swim、fly三個接口在不同動物類中重復實現(xiàn)一遍嗎?
其實dart里有個叫做mixin的概念可以解決上面的問題
mixin
mixin實際上也是面向?qū)ο缶幊讨械母拍?在維基百科上對它的解釋如下:
Mixin是面向?qū)ο蟪绦蛟O(shè)計語言中的類,提供了方法的實現(xiàn)。其他類可以訪問mixin類的方法而不必成為其子類。[1]Mixin有時被稱作"included"而不是"inherited"。mixin為使用它的class提供額外的功能,但自身卻不單獨使用(不能單獨生成實例對象,屬于抽象類)。因為有以上限制,Mixin類通常作為功能模塊使用,在需要該功能時“混入”,而且不會使類的關(guān)系變得復雜。用戶與Mixin不是“is-a”的關(guān)系,而是“-able”關(guān)系
dart語言里面我們可以使用with關(guān)鍵字實現(xiàn)mixin,將一個或者多個class混入另一個類:
class Base1 {
void foo1() {
print("foo1");
}
}
class Base2 {
void foo2() {
print("foo2");
}
}
class Child2 with Base1, Base2 {
}
沒錯,通過with多個類,可以實現(xiàn)類似多繼承的效果。
既然允許with多個類,那么如果這些類中有個相同方法,那會出現(xiàn)什么事情。實際上kotlin、java8使用接口的默認實現(xiàn)也會出現(xiàn)一樣的問題,他們的處理方法是當出現(xiàn)相同方法的時候?qū)崿F(xiàn)類需要手動指定使用哪個接口的默認實現(xiàn),要不然編譯會報錯:
// 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()
}
}
線性化
而在dart with里面越后面的類優(yōu)先級越高:
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 {}
這個時候調(diào)用Child.foo方法實際會優(yōu)先調(diào)用Base3.foo。原因是dart實際是通過創(chuàng)建中間類繼承實現(xiàn)的mixin,上面的代碼相當于:

通過從左到右的順序生成中間父類去繼承將extends、with線性化成一個單繼承鏈。所以Base2、Base3實際上不是Child的父類
mixin關(guān)鍵字
在上面的例子中我們使用普通的class去with,但dart實際上提供了一個mixin關(guān)鍵字,它定義了不能實例化,也不能extends只能with的類:
mixin Base {
}
// 編譯失敗: mixin類不能extends
// class Child extends Base {
//
// }
// 編譯成功: mixin類可以with
class Child with Base {
}
void main() {
// 編譯失敗: mixin類不能實例化
// Base()
}
這樣的類實際上和java、kotlin里面的interface已經(jīng)很像了。
另外我們可以通過mixin ... on 限定某個類只能由某些類去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,那就以為著這個with Base3的類一定是繼承或者with了 Base1,所以可以調(diào)用這個類的super.foo方法。要注意的是,這個super.foo并不指定一定調(diào)用的是Base1.foo。例如上面的代碼調(diào)用Child().foo()之后的打印實際上是:
2
3
它們線性化的到的繼承關(guān)系和前面全是class的代碼并沒有差別:

從上面的uml圖我們就能理解為什么打印是23了
理解了這個簡單的例子之后我們再來看一個復雜一點的例子:
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實際上調(diào)用的是Base1.foo1,而Base1.foo1中的foo2,由于繼承的多態(tài)特性,調(diào)用的是Base2.foo2。我們可以通過下面uml圖輔助理解,注意看繼承關(guān)系里面是沒有Base1、Base2的因為它們都是通過with混入的,并不是Child的父類:

with的類不能有構(gòu)造函數(shù)
另外,with的class和mixin類型都是不允許有構(gòu)造函數(shù)的,因為mixin機制語義上是向一個類混入其他類的方法或者成員變量,使得我們可以在混合類中訪問到混入類的方法或者屬性。而混入其他類的構(gòu)造函數(shù)實際上是沒有意義的,因為不會有人手動去調(diào)用這個混入類的構(gòu)造函數(shù)。
class Base1 {
Base1() {}
}
// 編譯失敗: 不能with一個帶有構(gòu)造函數(shù)的類
// class Child with Base1 {}
// 編譯失敗: mixin類型只能with,所以不能有構(gòu)造函數(shù)
// mixin Base2 {
// Base2() {}
// }
參考博客
https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3