【譯】Dart | 什麼是Mixin
原文連結: ofollow,noindex">medium.com/flutter-com…
當我開始學習Dart時,mixins對我來說是一個新的的概念。 我從C#轉過來,Mixin這個概念是不存在的(據我所知,至少在C#8.0之前不存在)。 起初,我發現這個概念有點難以理解,直到現在我才意識到它有多麼強大。
免責宣告:Mixins在Dart 2中不斷髮展。本文一些內容未來可能將會不再適用。
為什麼我們需要Mixin
我們先來看看下面的類繼承圖:

我們這裡有一個名為Animal的超類,它有三個子類(Mammal,Bird和Fish)。在底部,我們有具體的一些子類。 小方塊代表行為。例如,藍色方塊表示具有此行為的類的例項可以swim。
有些動物有共同的行為:貓和鴿子都可以行走,但是貓不能飛(除了Nyan Cat:grinning:)。 這些行為與此分類正交,因此我們無法在超類中實現這些行為。
如果一個類可以擁有多個超類,那就很容易辦到了。我們可以建立另外三個類:Walker,Swimmer,Flyer。在那之後,我們只需從Walker類繼承Dove和Cat。但在Dart中,每個類(除了Object類)都只有一個超類。
我們可以實現它,而不是繼承自Walker類,因為它是一個介面,但我們必須在多個類中實現行為,因此它並不是一個好的解決方案。
我們需要一種在多個類層次結構中重用類的程式碼的方法。 Mixin就能夠辦到這一點!
'Mixins are a way of reusing a class’s code in multiple class hierarchies. —dartlang.org'
:lock:限制
mixin功能有一些限制(來自dartlang.org):
- 在Dart 1.12或更低版本使用Mixin時必須繼承至Object,並且不能呼叫super()方法。
- SuperMixin :Dart 1.13或更高版本支援繼承至Object以外的類並使用Mixin,而且可以呼叫super.method()。這項支援僅在DartVM中預設開啟,並且在標誌後面的Analyzer中才能夠使用。更具體地說,它位於命令列分析器中的--supermixin標誌之後。它也可以在分析伺服器中,在客戶端可配置選項後面。Dart2js和dartdevc不支援SuperMixin。
- 在Dart 2.1中,mixins預計會有更少的限制。例如,Flutter支援mixins呼叫super()並從Object以外的類擴充套件,但是這些在出現在所有Dart SDK中之前,語法有可能會發生變化。
:memo:語法
我們明白了mixins為什麼如此有用,下面讓我們看看如何建立和使用它們。
Mixins通過普通的類宣告隱式定義:
class Walker { void walk() { print("I'm walking"); } } 複製程式碼
如果我們不想讓我們建立的mixin被例項化或擴充套件,我們可以像這樣定義它:
abstract class Walker { // This class is intended to be used as a mixin, and should not be // extended directly. factory Walker._() => null; void walk() { print("I'm walking"); } } 複製程式碼
要使用mixin的話,你需要使用with關鍵字,後跟一個或多個mixin的名稱:
class Cat extends Mammal with Walker {} class Dove extends Bird with Walker, Flyer {} 複製程式碼
我在Cat類上定義了Walker mixin,它允許我們呼叫walk方法而不是fly方法(在Flyer中定義)。
main(List<String> arguments) { Cat cat = Cat(); Dove dove = Dove(); // A cat can walk. cat.walk(); // A dove can walk and fly. dove.walk(); dove.fly(); // A normal cat cannot fly. // cat.fly(); // Uncommenting this does not compile. } 複製程式碼
:mag_right: 詳情
我告訴過你我發現這個概念有點難以理解,但是到目前為止它看上去並不那麼難是嗎?
哈哈。
那麼,你能告訴我們以下程式的輸出是什麼嗎:dizzy_face:?
class A { String getMessage() => 'A'; } class B { String getMessage() => 'B'; } class P { String getMessage() => 'P'; } class AB extends P with A, B {} class BA extends P with B, A {} void main() { String result = ''; AB ab = AB(); result += ab.getMessage(); BA ba = BA(); result += ba.getMessage(); print(result); } 複製程式碼
AB和BA類都使用A和B mixins繼承至P類,但順序不同。 所有的A(3個),B和P類都有一個名為getMessage的方法。
首先,我們呼叫AB類的getMessage方法,然後呼叫BA類的getMessage方法。
那麼你認為,結果是什麼?
- A. It does not compile
- B. BA
- C. AB
- D. BAAB
- E. ABBA
...
噹噹噹~答案是B!這個程式將列印BA。
我想你在猜測mixins的宣告順序非常重要。
Why?它是如何工作的?
:sparkles:線性化
當您將mixin混入類中時,請記住下面這句話:
'Dart中的Mixins通過建立一個新類來實現,該類將mixin的實現層疊在一個超類之上以建立一個新類 ,它不是“在超類中”,而是在超類的“頂部”,因此如何解決查詢問題不會產生歧義。
— Lasse R. H. Nielsen on StackOverflow.'
實際上,這段程式碼
class AB extends P with A, B {} class BA extends P with B, A {} 複製程式碼
在語義上等同於
class PA = P with A; class PAB = PA with B; class AB extends PAB {} class PB = P with B; class PBA = PB with A; class BA extends PBA {} 複製程式碼
最終的繼承關係可以用下圖表示:

在AB和P之間建立新類,這些新類是超類P與A類和B類之間的混合類。
正如你所看到的那樣,我們並沒有使用多重繼承!
- Mixins不是一種在經典意義上獲得多重繼承的方法。
- Mixins是一種抽象和重用一系列操作和狀態的方法。
- 它類似於擴充套件類所獲得的重用,但它與單繼承相容,因為它是線性的。
— Lasse R. H. Nielsen on StackOverflow.
宣告mixins的順序代表了從最高階到最高階的繼承鏈,這件事非常重要,你需要記住。
:page_facing_up:型別
mixin應用程式例項的型別是什麼?
通常,它是其超類的子型別,也是mixin名稱本身表示的類的子型別,即原始類的型別。 —dartlang.org
所以這意味著這個程式
class A { String getMessage() => 'A'; } class B { String getMessage() => 'B'; } class P { String getMessage() => 'P'; } class AB extends P with A, B {} class BA extends P with B, A {} void main() { AB ab = AB(); print(ab is P); print(ab is A); print(ab is B); BA ba = BA(); print(ba is P); print(ba is A); print(ba is B); } 複製程式碼
將在控制檯中列印六行true。
詳細解釋
Lasse R. H. Nielsen給了我一個很棒的解釋:
由於每個mixin應用程式都建立一個新類,它還會建立一個新介面(因為所有Dart類也定義了介面)。 如上所述,新類擴充套件了超類幷包含了mixin類成員的副本,但它也實現了mixin類介面。
在大多數情況下,無法引用mixin-application類或其介面。
Super with Mixin的類只是類的匿名超類,宣告類似C類使用Mixin {}擴充套件Super。 如果你將一個mixin應用程式命名為類CSuper = Super with Mixin {},那麼你可以參考mixin應用程式類及其介面,它將是Super和Mixin的子型別。
— Lasse R. H. Nielsen
什麼時候應該使用mixins?
當我們想要在不共享相同類層次結構的多個類之間共享行為時,或者在超類中實現此類行為沒有意義時,Mixins非常有用。
通常情況下是序列化(例如,檢視jaguar_serializer)或持久化。 但是你也可以使用mixins來提供一些實用功能(比如Flutter中的RenderSliverHelpers)。
花點時間玩這個功能,我相信你會找到新的用例:wink:。 不要侷限於無狀態mixins,你絕對可以儲存變數並使用它們!
:chart_with_upwards_trend:Mixins的規範正在發展
如果你對Dart語言的演變感興趣,你應該知道它的規範將在Dart 2.1中發展,他們會喜歡你的反饋。 有關詳細資訊,請閱讀 此內容 。
為了讓您瞭解未來的一些趨勢,請考慮以下原始碼:
abstract class Super { void method() { print("Super"); } } class MySuper implements Super { void method() { print("MySuper"); } } mixin Mixin on Super { void method() { super.method(); print("Sub"); } } class Client extends MySuper with Mixin {} void main() { Client().method(); } 複製程式碼
第13行到第18行的mixin宣告表示Super上的超類約束。 這意味著為了將這個mixin用在這裡,這個類必須繼承或實現Super,因為mixin使用了Super提供的功能。
這個程式的輸出是:
MySuper Sub 複製程式碼
如果你想知道為什麼,請回憶mixins是如何線性化的:

第15行呼叫super.method()實際上呼叫了第8行宣告的方法。
:dolphin:完整的 Animal example
你可以在下面找到我用它介紹mixins的完整示例:
abstract class Animal {} abstract class Mammal extends Animal {} abstract class Bird extends Animal {} abstract class Fish extends Animal {} abstract class Walker { // This class is intended to be used as a mixin, and should not be // extended directly. factory Walker._() => null; void walk() { print("I'm walking"); } } abstract class Swimmer { // This class is intended to be used as a mixin, and should not be // extended directly. factory Swimmer._() => null; void swim() { print("I'm swimming"); } } abstract class Flyer { // This class is intended to be used as a mixin, and should not be // extended directly. factory Flyer._() => null; void fly() { print("I'm flying"); } } class Dolphin extends Mammal with Swimmer {} class Bat extends Mammal with Walker, Flyer {} class Cat extends Mammal with Walker {} class Dove extends Bird with Walker, Flyer {} class Duck extends Bird with Walker, Swimmer, Flyer {} class Shark extends Fish with Swimmer {} class FlyingFish extends Fish with Swimmer, Flyer {} 複製程式碼
我們可以在下面看到這些mixin是如何線性化的:

:green_book:總結
我們看到mixins是一個強大的概念,允許您跨多個類層次結構重用程式碼。
Flutter經常使用到這個功能,我覺得更好地理解它非常重要,這就是為什麼我跟你分享我的理解。
感謝Jeroen Meijer的校對。
如果您對Mixin十分感興趣,歡迎在下方與我留言,或者聯絡我,我們一起討論!:smiley: