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è)整體了解滋迈。