深入理解Dart Mixin

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

1.png

由于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,上面的代碼相當于:

2.png

通過從左到右的順序生成中間父類去繼承將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的代碼并沒有差別:

3.png

從上面的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的父類:

4.png

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

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

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

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

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