flutter mixin探秘
本文是根據(jù)flutter v1.9.1版本分析編寫。依賴的dart版本是V2.5.0
本文分為兩個部分,第一部分介紹mixin的使用,第二部分是with的實現(xiàn)原理。
一、mixin的用法
在最近看flutter SDK中的源碼時對其中復(fù)雜的mixin和on搞的糊里糊涂的,所以就去詳細了解了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ì)是繼承還是組合還是它們的變體或者其它新穎的實現(xiàn)方式呢?這里賣個關(guān)子,我們繼續(xù)看下去。
下面首先介紹和mixin使用相關(guān)的一些dart保留字。
1.1 保留字介紹
- mixin:
mixin用來聲明一個“類”,其中除了不能聲明構(gòu)造函數(shù),其它和一個普通類沒有區(qū)別。
mixin mixinM {
//error
mixinM();
int i;
void test() {}
}
- with:
使用with關(guān)鍵字,后跟一個或多個mixin或者普通類。
ps:本文分析時,with后面可以跟mixin或者類(類中不能有構(gòu)造方法),但在官方更新計劃文檔中,會在后續(xù)版本中把mixin和class進一步隔離開來,with后面不能在后面聲明類,為了減少后續(xù)代碼的適配成本,大家開發(fā)時多加注意(不過適配成本并不大)
class ClassDemo with mixinM {
String name;
ClassDemo();
void testClass() {}
}
- on的解釋
要指定只有某些特定類型可以使用mixin,使用on來指定所需的超類或者mixin,可以讓你編寫的mixin可以調(diào)用它未定義的方法,并且可以使用super像繼承一樣調(diào)用父類方法。
如下面代碼所示,詳細看代碼注釋。
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和普通繼承的使用非常像,我們定義一個mixin,在要使用的地方對其進行with,就可以使用mixin中聲明的方法或者變量,那么他具體和繼承有什么區(qū)別呢?
首先,當(dāng)你的類去with一個mixin/類時,他并不影響你再去繼承一個其它的類,如下:
class A extends T with B, C {}
其次在上面我們也說了,mixin本身是個打了引號的類,他不能聲明構(gòu)造方法,說明官方不希望我們編寫初始化它的代碼,我們不能通過初始化獲得它的引用,并通過引用在其他的地方調(diào)用它的方法;要使用它我們只能通過with的方式,把他混入到某個類上。
這個是因為如果這樣使用是不安全的,上面我們說到,mixin中可以調(diào)用在其本身未聲明的方法,可以通過on的方式帶來來了一種擴展mixin本身能力的方式,但是前提是我們混入的類需要繼承或者with mixin上on的類型,當(dāng)我們直接引用而不是通過with的方式,這種調(diào)用可以能會使用到未聲明的方法,產(chǎn)生運行安全問題;
二是因為因為在底層的實現(xiàn)上不允許這樣的使用,文章第二部分會分析到。
上面可以看到,with后面是可以添加多個mixin類型的,那么它是一種“多繼承”么?我們看下面這個例子。
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”才是,這個with帶來的效果好像是“遠者近也”,什么意思呢?我們知道在繼承中,在類的層次結(jié)構(gòu)中方法的父類方法調(diào)用實際上調(diào)用的誰離類本身父類層級中最近的類中的方法,但是with相反,難道它顛覆了面向?qū)ο笾欣^承的實現(xiàn)么,起碼表象看來如此,但是實際如此么?
這個mixin到底是什么的變種呢?其實還是離不開繼承與實現(xiàn),mixin本身以及with和on的底層實現(xiàn)都是通過繼承以及實現(xiàn)接口的方式實現(xiàn)的,那么這種“遠者近也”是怎么產(chǎn)生的呢,在本文下面第二個部分“脫糖”中我們詳細描述。
二、mixin的脫糖
承接上文,我們知道了mixin的使用,但是對于其中的一些具體細節(jié)還是有一些疑惑,接下來我們會具體介紹其底層原理,也即mixin的脫糖過程。
在flutter的編譯過程中,dart代碼在編譯過程中會會被先編譯成dill文件,分析這個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時需要繼承或者實現(xiàn)on后面的類型,它們都被當(dāng)作接口implements。mixin上省略的on子句等效于on Object。
mixin會被轉(zhuǎn)化為抽象類,其on的類型會被以普通接口的方式實現(xiàn),但是為了字節(jié)碼的大小問題,我們用抽象類,可以不用實現(xiàn)on的類型中的方法,這也說明了上面我們說為什么我們不能直接使用mixin,它沒有構(gòu)造方法的問題,因為它的底層實現(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形成的混合類給捋直了,還是倒著捋的,這也回答了上面的例子的問題,“遠者近也”確實只是表象內(nèi)容,其內(nèi)在的實現(xiàn)還是繼承和實現(xiàn)那一套。
只是為什么是倒著來的呢,為了保證mixin“類”代碼調(diào)用不屬于它的擴展的代碼,也即on的類型的代碼,我么需要把on的對象放到類層級上層(也包括extends的對象),那么即使with是正的順序,和extends的順序就會分割開來,導(dǎo)致with是正的,但是extends在前,但實際在類層級結(jié)構(gòu)中的上面,就會帶來更多的歧義,所以還不如統(tǒng)一倒著來。
從上面也可以看到,在脫糖過程中為了捋順代碼層級結(jié)構(gòu),會增加許多私有的抽象類,它們主要負責(zé)連接mixin,以及承擔(dān)在implements中方法的實現(xiàn),其實就是方法拷貝。
在我閱讀sdk中使用的一些復(fù)雜的mixin使用,它的易讀性非常差,因為他本質(zhì)是繼承,所以mixin中可以重寫on 后面的類或者mixin中方法,也可以通過super調(diào)用on中被重寫的方法,這進一步增難了代碼的閱讀以及理解;并且在使用mixin時,形成的繼承結(jié)構(gòu)導(dǎo)致,with的對象要按照mixin本身在后,其on的類/mixin在前的順序排列,其實也增加了維護和迭代的成本。我本身覺得其確實在編寫簡單的邏輯上上可以減少代碼的編寫,比接口好用,比繼承有更多的功能,但這都是犧牲了,比如上面提到的一些東西的。
所以我編寫這篇文章,希望能幫助你在使用以及在閱讀別人的mixin代碼時,更加輕松,對其真正的實現(xiàn)也有個整體了解。