flutter mixin探秘

flutter mixin探秘


本文是根據(jù)flutter v1.9.1版本分析編寫。依賴的dart版本是V2.5.0

本文分為兩個(gè)部分赂弓,第一部分介紹mixin的使用,第二部分是with的實(shí)現(xiàn)原理。

一厦画、mixin的用法

在最近看flutter SDK中的源碼時(shí)對其中復(fù)雜的mixin和on搞的糊里糊涂的,所以就去詳細(xì)了解了mixin的用法翘紊。
在了解如何使用mixin之前换薄,先簡單介紹下mixin,下面是官方的解釋:

Mixins are a way of reusing a class’s code in multiple class hierarchies.

mixin提供了一種在復(fù)雜類層次結(jié)構(gòu)中復(fù)用代碼的方法融师。
我們知道右钾,F(xiàn)lutter使用的Dart語言是一種面向?qū)ο笳Z言,在面向?qū)ο笾锌梢詮?fù)用代碼的方式有繼承和組合兩種常用的方式旱爆,那么mixin本質(zhì)是繼承還是組合還是它們的變體或者其它新穎的實(shí)現(xiàn)方式呢舀射?這里賣個(gè)關(guān)子,我們繼續(xù)看下去怀伦。
下面首先介紹和mixin使用相關(guān)的一些dart保留字脆烟。

1.1 保留字介紹
  • mixin:
    mixin用來聲明一個(gè)“類”,其中除了不能聲明構(gòu)造函數(shù)房待,其它和一個(gè)普通類沒有區(qū)別邢羔。
mixin mixinM {
 //error
 mixinM();

 int i;

 void test() {}
}
  • with:
    使用with關(guān)鍵字,后跟一個(gè)或多個(gè)mixin或者普通類吴攒。
    ps:本文分析時(shí)张抄,with后面可以跟mixin或者類(類中不能有構(gòu)造方法),但在官方更新計(jì)劃文檔中洼怔,會在后續(xù)版本中把mixin和class進(jìn)一步隔離開來署惯,with后面不能在后面聲明類,為了減少后續(xù)代碼的適配成本镣隶,大家開發(fā)時(shí)多加注意(不過適配成本并不大)
class ClassDemo with mixinM {
  String name;

  ClassDemo();

  void testClass() {}
}
  • on的解釋
    要指定只有某些特定類型可以使用mixin极谊,使用on來指定所需的超類或者mixin诡右,可以讓你編寫的mixin可以調(diào)用它未定義的方法,并且可以使用super像繼承一樣調(diào)用父類方法轻猖。
    如下面代碼所示帆吻,詳細(xì)看代碼注釋。
mixin mixinA {
  testA() {}
}

mixin mixinM on mixinA {
  
  int fieldM;

  void test() {
    testA();//調(diào)用mixinA的testA方法
  }
}
//要使用mixinM咙边,必須要繼承mixinM on的類或者with mixinM on的mixin/類
class ClassDemo with mixinA, mixinM {
  String name;

  ClassDemo();

  void testClass() {
     test();// 使用mixinM的test方法
  }
}
1.2 使用詳解

在上面我們介紹了mixin使用中所用到的一些保留字猜煮,并在一些簡單的代碼中看到了mixin作為一種代碼邏輯復(fù)用的方式的使用。我們看到mixin和普通繼承的使用非常像败许,我們定義一個(gè)mixin王带,在要使用的地方對其進(jìn)行with,就可以使用mixin中聲明的方法或者變量市殷,那么他具體和繼承有什么區(qū)別呢愕撰?
首先,當(dāng)你的類去with一個(gè)mixin/類時(shí)醋寝,他并不影響你再去繼承一個(gè)其它的類搞挣,如下:
class A extends T with B, C {}
其次在上面我們也說了,mixin本身是個(gè)打了引號的類音羞,他不能聲明構(gòu)造方法囱桨,說明官方不希望我們編寫初始化它的代碼,我們不能通過初始化獲得它的引用黄选,并通過引用在其他的地方調(diào)用它的方法蝇摸;要使用它我們只能通過with的方式,把他混入到某個(gè)類上办陷。
這個(gè)是因?yàn)槿绻@樣使用是不安全的貌夕,上面我們說到,mixin中可以調(diào)用在其本身未聲明的方法民镜,可以通過on的方式帶來來了一種擴(kuò)展mixin本身能力的方式啡专,但是前提是我們混入的類需要繼承或者with mixin上on的類型,當(dāng)我們直接引用而不是通過with的方式制圈,這種調(diào)用可以能會使用到未聲明的方法们童,產(chǎn)生運(yùn)行安全問題;
二是因?yàn)橐驗(yàn)樵诘讓拥膶?shí)現(xiàn)上不允許這樣的使用,文章第二部分會分析到鲸鹦。
上面可以看到慧库,with后面是可以添加多個(gè)mixin類型的,那么它是一種“多繼承”么馋嗜?我們看下面這個(gè)例子齐板。

mixin A {
  test() {
    print('A');
  }
}
mixin B {
  test() {
    print('B');
  }
}

class C {
  test() {
    print('C');
  }
}

class D extends C with A, B {}

class E extends C with B, A {}

void main() {
  D d = D();
  d.test();

  E e = E();
  e.test();
}

上面的main方法最終print的是什么呢?
BA
這是什么情況呢,好像我們第一印象中應(yīng)該是“CC”才是甘磨,這個(gè)with帶來的效果好像是“遠(yuǎn)者近也”橡羞,什么意思呢?我們知道在繼承中济舆,在類的層次結(jié)構(gòu)中方法的父類方法調(diào)用實(shí)際上調(diào)用的誰離類本身父類層級中最近的類中的方法卿泽,但是with相反,難道它顛覆了面向?qū)ο笾欣^承的實(shí)現(xiàn)么滋觉,起碼表象看來如此签夭,但是實(shí)際如此么?
這個(gè)mixin到底是什么的變種呢椎侠?其實(shí)還是離不開繼承與實(shí)現(xiàn)覆致,mixin本身以及with和on的底層實(shí)現(xiàn)都是通過繼承以及實(shí)現(xiàn)接口的方式實(shí)現(xiàn)的,那么這種“遠(yuǎn)者近也”是怎么產(chǎn)生的呢肺蔚,在本文下面第二個(gè)部分“脫糖”中我們詳細(xì)描述。

二儡羔、mixin的脫糖

承接上文宣羊,我們知道了mixin的使用,但是對于其中的一些具體細(xì)節(jié)還是有一些疑惑汰蜘,接下來我們會具體介紹其底層原理仇冯,也即mixin的脫糖過程。
在flutter的編譯過程中族操,dart代碼在編譯過程中會會被先編譯成dill文件苛坚,分析這個(gè)dill文件我們發(fā)現(xiàn)了mixin以及with脫糖后的字節(jié)碼,下面我們一一介紹色难。

2.1 mixin“類”

在編譯產(chǎn)物中泼舱,我們發(fā)現(xiàn)程序中mixin代碼會做如下的轉(zhuǎn)化:
源碼:

mixin B {
  testB() {}
}
mixin C {
  testC() {}
}
mixin D {
  testD() {}
}
mixin A on B, C, D {
  testA() {
    testB();
    testC();
    testD();
  }
}

轉(zhuǎn)化后:

abstract class B extends Object {
  abstract testB() {}
}

abstract class C extends Object {
  abstract testC() {}
}

abstract class D extends Object {
  abstract testD() {}
}
//mixin A轉(zhuǎn)化
abstract class _A&B&C extends Object implements B, C{}
abstract class _A&B&C&D extends Object implements _A&B&C ,D {}
abstract class A extends _A&B&C&D {
  abstract testA() {
    testB();
    testC();
    testD();
  }
}

從上面的轉(zhuǎn)化過程中我們就知道了為什么當(dāng)on后面限定了類型,在使用mixin時(shí)需要繼承或者實(shí)現(xiàn)on后面的類型,它們都被當(dāng)作接口implements枷莉。mixin上省略的on子句等效于on Object娇昙。
mixin會被轉(zhuǎn)化為抽象類,其on的類型會被以普通接口的方式實(shí)現(xiàn)笤妙,但是為了字節(jié)碼的大小問題冒掌,我們用抽象類,可以不用實(shí)現(xiàn)on的類型中的方法蹲盘,這也說明了上面我們說為什么我們不能直接使用mixin股毫,它沒有構(gòu)造方法的問題,因?yàn)樗牡讓訉?shí)現(xiàn)是不允許的召衔,mixin只有被混入到普通類上铃诬,才能建立完整的類結(jié)構(gòu),下面我們就說下混入到的普通類上的mixin形成的混合類是如何脫糖的,以及怎樣建立完整的可使用的類結(jié)構(gòu)的氧急。

2.2 混合類

上面是mixin的脫糖后的代碼颗胡,那么混入mixin的普通類會被轉(zhuǎn)化為什么樣的代碼呢?
源碼:

mixin B {
  testB() {}
}
mixin C {
  testC() {}
}

mixin A on B, C {
  testA() {
    testB();
    testC();
  }
}

class E {}

class F extends E with B, C, A {
    testF(){}
}

脫糖后:

//類F轉(zhuǎn)化后的代碼吩坝,上面的同上省略了
abstract class _F&E&B extends E implements B{
    testB() {}
}
abstract class _F&E&B&C extends _F&E&B implements C{
    restC() {}
}
abstract class _F&E&B&C&A extends _F&E&B&C implements A{
    testA() {
        testB();
        testC();
    }
}
class F extends _F&E&B&C&A {
    testF(){}
}

從上面我們可以看到毒姨,dart把我們的繼承以及with形成的混合類給捋直了,還是倒著捋的钉寝,這也回答了上面的例子的問題弧呐,“遠(yuǎn)者近也”確實(shí)只是表象內(nèi)容,其內(nèi)在的實(shí)現(xiàn)還是繼承和實(shí)現(xiàn)那一套嵌纲。
只是為什么是倒著來的呢俘枫,為了保證mixin“類”代碼調(diào)用不屬于它的擴(kuò)展的代碼,也即on的類型的代碼逮走,我么需要把on的對象放到類層級上層(也包括extends的對象)鸠蚪,那么即使with是正的順序,和extends的順序就會分割開來师溅,導(dǎo)致with是正的茅信,但是extends在前,但實(shí)際在類層級結(jié)構(gòu)中的上面墓臭,就會帶來更多的歧義蘸鲸,所以還不如統(tǒng)一倒著來。
從上面也可以看到窿锉,在脫糖過程中為了捋順代碼層級結(jié)構(gòu)酌摇,會增加許多私有的抽象類,它們主要負(fù)責(zé)連接mixin嗡载,以及承擔(dān)在implements中方法的實(shí)現(xiàn)窑多,其實(shí)就是方法拷貝。
在我閱讀sdk中使用的一些復(fù)雜的mixin使用洼滚,它的易讀性非常差怯伊,因?yàn)樗举|(zhì)是繼承,所以mixin中可以重寫on 后面的類或者mixin中方法判沟,也可以通過super調(diào)用on中被重寫的方法耿芹,這進(jìn)一步增難了代碼的閱讀以及理解;并且在使用mixin時(shí)挪哄,形成的繼承結(jié)構(gòu)導(dǎo)致吧秕,with的對象要按照mixin本身在后,其on的類/mixin在前的順序排列迹炼,其實(shí)也增加了維護(hù)和迭代的成本砸彬。我本身覺得其確實(shí)在編寫簡單的邏輯上上可以減少代碼的編寫颠毙,比接口好用,比繼承有更多的功能砂碉,但這都是犧牲了蛀蜜,比如上面提到的一些東西的。
所以我編寫這篇文章增蹭,希望能幫助你在使用以及在閱讀別人的mixin代碼時(shí)滴某,更加輕松,對其真正的實(shí)現(xiàn)也有個(gè)整體了解滋迈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霎奢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子饼灿,更是在濱河造成了極大的恐慌幕侠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碍彭,死亡現(xiàn)場離奇詭異晤硕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庇忌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門窗骑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人漆枚,你說我怎么就攤上這事〉种” “怎么了墙基?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刷喜。 經(jīng)常有香客問我残制,道長,這世上最難降的妖魔是什么掖疮? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任初茶,我火速辦了婚禮,結(jié)果婚禮上浊闪,老公的妹妹穿的比我還像新娘恼布。我一直安慰自己,他們只是感情好搁宾,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布折汞。 她就那樣靜靜地躺著,像睡著了一般盖腿。 火紅的嫁衣襯著肌膚如雪爽待。 梳的紋絲不亂的頭發(fā)上损同,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音鸟款,去河邊找鬼膏燃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛何什,可吹牛的內(nèi)容都是我干的组哩。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼富俄,長吁一口氣:“原來是場噩夢啊……” “哼禁炒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霍比,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤幕袱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后悠瞬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體们豌,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年浅妆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了望迎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凌外,死狀恐怖辩尊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情康辑,我是刑警寧澤摄欲,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站疮薇,受9級特大地震影響胸墙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜按咒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一迟隅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧励七,春花似錦智袭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剿另,卻和暖如春箫锤,著一層夾襖步出監(jiān)牢的瞬間贬蛙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工谚攒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阳准,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓馏臭,卻偏偏與公主長得像野蝇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子括儒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容