flutter mixin探秘

flutter mixin探秘


本文是根据flutter v1.9.1版本分析编写。依赖的dart版本是V2.5.0

本文分为两个部分,第一部分介绍mixin的使用,第二部分是with的实现原理。

一、mixin的用法

在最近看flutter SDK中的源码时对其中复杂的mixin和on搞的糊里糊涂的,所以就去详细了解了mixin的用法。
在了解如何使用mixin之前,先简单介绍下mixin,下面是官方的解释:

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

mixin提供了一种在复杂类层次结构中复用代码的方法。
我们知道,Flutter使用的Dart语言是一种面向对象语言,在面向对象中可以复用代码的方式有继承和组合两种常用的方式,那么mixin本质是继承还是组合还是它们的变体或者其它新颖的实现方式呢?这里卖个关子,我们继续看下去。
下面首先介绍和mixin使用相关的一些dart保留字。

1.1 保留字介绍
  • mixin:
    mixin用来声明一个“类”,其中除了不能声明构造函数,其它和一个普通类没有区别。
mixin mixinM {
 //error
 mixinM();

 int i;

 void test() {}
}
  • with:
    使用with关键字,后跟一个或多个mixin或者普通类。
    ps:本文分析时,with后面可以跟mixin或者类(类中不能有构造方法),但在官方更新计划文档中,会在后续版本中把mixin和class进一步隔离开来,with后面不能在后面声明类,为了减少后续代码的适配成本,大家开发时多加注意(不过适配成本并不大)
class ClassDemo with mixinM {
  String name;

  ClassDemo();

  void testClass() {}
}
  • on的解释
    要指定只有某些特定类型可以使用mixin,使用on来指定所需的超类或者mixin,可以让你编写的mixin可以调用它未定义的方法,并且可以使用super像继承一样调用父类方法。
    如下面代码所示,详细看代码注释。
mixin mixinA {
  testA() {}
}

mixin mixinM on mixinA {
  
  int fieldM;

  void test() {
    testA();//调用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作为一种代码逻辑复用的方式的使用。我们看到mixin和普通继承的使用非常像,我们定义一个mixin,在要使用的地方对其进行with,就可以使用mixin中声明的方法或者变量,那么他具体和继承有什么区别呢?
首先,当你的类去with一个mixin/类时,他并不影响你再去继承一个其它的类,如下:
class A extends T with B, C {}
其次在上面我们也说了,mixin本身是个打了引号的类,他不能声明构造方法,说明官方不希望我们编写初始化它的代码,我们不能通过初始化获得它的引用,并通过引用在其他的地方调用它的方法;要使用它我们只能通过with的方式,把他混入到某个类上。
这个是因为如果这样使用是不安全的,上面我们说到,mixin中可以调用在其本身未声明的方法,可以通过on的方式带来来了一种扩展mixin本身能力的方式,但是前提是我们混入的类需要继承或者with mixin上on的类型,当我们直接引用而不是通过with的方式,这种调用可以能会使用到未声明的方法,产生运行安全问题;
二是因为因为在底层的实现上不允许这样的使用,文章第二部分会分析到。
上面可以看到,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
这是什么情况呢,好像我们第一印象中应该是“CC”才是,这个with带来的效果好像是“远者近也”,什么意思呢?我们知道在继承中,在类的层次结构中方法的父类方法调用实际上调用的谁离类本身父类层级中最近的类中的方法,但是with相反,难道它颠覆了面向对象中继承的实现么,起码表象看来如此,但是实际如此么?
这个mixin到底是什么的变种呢?其实还是离不开继承与实现,mixin本身以及with和on的底层实现都是通过继承以及实现接口的方式实现的,那么这种“远者近也”是怎么产生的呢,在本文下面第二个部分“脱糖”中我们详细描述。

二、mixin的脱糖

承接上文,我们知道了mixin的使用,但是对于其中的一些具体细节还是有一些疑惑,接下来我们会具体介绍其底层原理,也即mixin的脱糖过程。
在flutter的编译过程中,dart代码在编译过程中会会被先编译成dill文件,分析这个dill文件我们发现了mixin以及with脱糖后的字节码,下面我们一一介绍。

2.1 mixin“类”

在编译产物中,我们发现程序中mixin代码会做如下的转化:
源码:

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

转化后:

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

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

abstract class D extends Object {
  abstract testD() {}
}
//mixin A转化
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();
  }
}

从上面的转化过程中我们就知道了为什么当on后面限定了类型,在使用mixin时需要继承或者实现on后面的类型,它们都被当作接口implements。mixin上省略的on子句等效于on Object。
mixin会被转化为抽象类,其on的类型会被以普通接口的方式实现,但是为了字节码的大小问题,我们用抽象类,可以不用实现on的类型中的方法,这也说明了上面我们说为什么我们不能直接使用mixin,它没有构造方法的问题,因为它的底层实现是不允许的,mixin只有被混入到普通类上,才能建立完整的类结构,下面我们就说下混入到的普通类上的mixin形成的混合类是如何脱糖的,以及怎样建立完整的可使用的类结构的。

2.2 混合类

上面是mixin的脱糖后的代码,那么混入mixin的普通类会被转化为什么样的代码呢?
源码:

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转化后的代码,上面的同上省略了
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形成的混合类给捋直了,还是倒着捋的,这也回答了上面的例子的问题,“远者近也”确实只是表象内容,其内在的实现还是继承和实现那一套。
只是为什么是倒着来的呢,为了保证mixin“类”代码调用不属于它的扩展的代码,也即on的类型的代码,我么需要把on的对象放到类层级上层(也包括extends的对象),那么即使with是正的顺序,和extends的顺序就会分割开来,导致with是正的,但是extends在前,但实际在类层级结构中的上面,就会带来更多的歧义,所以还不如统一倒着来。
从上面也可以看到,在脱糖过程中为了捋顺代码层级结构,会增加许多私有的抽象类,它们主要负责连接mixin,以及承担在implements中方法的实现,其实就是方法拷贝。
在我阅读sdk中使用的一些复杂的mixin使用,它的易读性非常差,因为他本质是继承,所以mixin中可以重写on 后面的类或者mixin中方法,也可以通过super调用on中被重写的方法,这进一步增难了代码的阅读以及理解;并且在使用mixin时,形成的继承结构导致,with的对象要按照mixin本身在后,其on的类/mixin在前的顺序排列,其实也增加了维护和迭代的成本。我本身觉得其确实在编写简单的逻辑上上可以减少代码的编写,比接口好用,比继承有更多的功能,但这都是牺牲了,比如上面提到的一些东西的。
所以我编写这篇文章,希望能帮助你在使用以及在阅读别人的mixin代码时,更加轻松,对其真正的实现也有个整体了解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容