前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解Flutter中的Mixin机制

深入理解Flutter中的Mixin机制

作者头像
BennuCTech
发布2023-10-09 14:52:44
3730
发布2023-10-09 14:52:44
举报
文章被收录于专栏:BennuCTechBennuCTech

前言

Mixin到底是什么?Mixin是解决代码重用的一种方案,类似多继承。我们知道在dart中是单继承的,但是有些情况单继承就会显得不够用。

比如我们为鸟建立一个类Bird:

代码语言:javascript
复制
class Bird{
  void fly(){
  }
  void bear(){
    print("ovoid");//卵生
  }
}

再为哺乳动物建立一个类:

代码语言:javascript
复制
class Mammal{
  void bear(){
    print("viviparous");//胎生
  }
}

大部分情况下这两个类是没有问题的,但是总有个例,比如蝙蝠。它的生育是胎生,但是它像鸟一样会飞,如何继承这两种特性?

针对这种情况,dart提供了Mixin机制。

下面看看Mixin如何使用。

混入with

我们先定义一个类A:

代码语言:javascript
复制
class A{
  void init(){
    print("a");
  }
}

然后可以通过with关键字来实现混入,如下:

代码语言:javascript
复制
class MixinObj with A{
}

这样MixinObj就继承了init函数,执行MixinObj().init()就会打印出“a”。

也可以是抽象类,如下:

代码语言:javascript
复制
abstract class A{
  void init(){
    print("a");
  }
}

也可以是mixin,如下:

代码语言:javascript
复制
mixin A{
  void init(){
    print("a");
  }
}

注意with继承的类(A):

  • 不能声明任何构造函数,即使是无参构造函数,否则会编译报错。
  • 不能通过extends(implements可以)继承任何类,否则编译报错。

如果MixinObj中重写对应函数,那么就会覆盖,如下:

代码语言:javascript
复制
class MixinObj with A{
  @override
  void init(){
    print("obj");
  }
}

这时候只会打印"obj",但是如果执行了super,如下:

代码语言:javascript
复制
class MixinObj with A{
  @override
  void init(){
    super.init();  
    print("obj");
  }
}

那么A的对应函数也会执行,就会打印“a"和“obj”。

混入多个类

上面MixinObj只混入了一个类,那么混入多个类呢?

我们再定义一个类B:

代码语言:javascript
复制
class B{
  void init(){
    print("b");
  }

  void b(){
    print("bb");
  }
}

然后混入MixinObj,如下:

代码语言:javascript
复制
class MixinObj with A, B{
}

这样MixinObj不仅有init函数,还多了一个b函数。

但是这里有一个问题,A和B都有init函数,那么在不重写的情况下执行哪个?

答案是最后一个生效,所以是执行了B的init函数。同样如果重写了但是执行了super,也会执行B的init函数。

这是Mixin的原则,如果有冲突的话,后面的会优先于前面的,如果没有冲突则会全部保留。这里的优先于如何理解?我们后面深入分析。

有继承的情况

如果有MixinObj有父类的情况呢?我们修改代码如下:

代码语言:javascript
复制
class MixinObj extends A with B{

}

同样A和B都有init,这时候依然会执行B的init函数。同理如果重写其实也是重写B的init函数。

那么如果有接口的情况呢?比如:

代码语言:javascript
复制
class MixinObj extends A with B implements C{

}

这里C是一个抽象类,有一个init抽象函数。

执行后发现还是B的init函数生效了,所以implements不影响with,而with会影响extends。为什么?后面重点分析。

注意:extends、with、implements三个关键字是有严格顺序的:extends->with->implements,如果顺序不对就会编译出错

原理

前面我们看with可以混入多个类,同时还对extends有影响,那么这里面到底是什么原理?

为了更好的理解Mixin的机制,我们再定义一个类C如下:

代码语言:javascript
复制
class C{
  void init(){
    print("c");
  };
}

然后修改MixinObj:

代码语言:javascript
复制
class MixinObj extends C with A, B{
  @override
  void init(){
    super.init();
    print("obj");
  }
}

然后运行起来,发现打印出来的是“b”和"obj",super.init()竟然执行的不是C的init函数。

为了搞清楚这里面的关系,我们需要从编译生成的文件源码入手。

我这里是通过Web运行的,运行后可以在开发者工具的Source中找到源码,如图:

可以看到在MixinObj.dart文件同目录下又生成了一个同名的lib.js文件,这个就是最终执行的代码,来看看其中部分代码:

代码语言:javascript
复制
const C_A$36 = class C_A extends MixinTest.C {};
  (C_A$36.new = function() {
  }).prototype = C_A$36.prototype;
  dart.applyMixin(C_A$36, MixinTest.A);
  
  const C_B$36 = class C_B extends C_A$36 {};
  (C_B$36.new = function() {
  }).prototype = C_B$36.prototype;
  dart.applyMixin(C_B$36, MixinTest.B);
  
  MixinTest.MixinObj = class MixinObj extends C_B$36 {
    init() {
      super.init();
      core.print("obj");
    }
  };

可以看到新生成了两个类C_A36和C_B36,这两个类就是分别基于A和B生成的(dart.applyMixin(C_A

而上面我提到过with后面的类是不允许extends继承的,所以就不会干扰整个继承链路,就不用考虑中间还有继承的情况了。

简单来说,Mixin就是将类和它的父类(如果没有就是Objecr)的继承关系之间又插入了一些继承关系,这些继承的类的顺序是从右向左的。正是这个继承链才实现了Mixin所谓的多继承

mixin继承:on

上面我们知道可以通过呢mixin关键字定义一个mixin结构,它还可以配合on关键字使用。

这个on类似于extends,是一种继承关系,后面的父类可以是mixin,也可以是calss。

当使用on关键字,则表示该mixin只用于它父类的子类

下面用例子来更好的理解。

我们先定义一个类如下:

代码语言:javascript
复制
class MixinBase{
  void init(){
    print("base");
  }
}

然后定义一个Mixin类,如下:

代码语言:javascript
复制
mixin MixinA on MixinBase{
  @override
  void init(){
    super.init();
    print("mixina");
  }
}

这里我们来看看生成的源码:

代码语言:javascript
复制
MixinTest.MixinA = class MixinA extends MixinTest.MixinBase {};
  MixinTest.MixinA[dart.mixinOn] = MixinBase => class MixinA extends MixinBase {
    init() {
      super.init();
      core.print("mixina");
    }
  };
  dart.addTypeTests(MixinTest.MixinA);
  dart.addTypeCaches(MixinTest.MixinA);
  MixinTest.MixinA[dart.implements] = () => [MixinTest.MixinBase];
  dart.setLibraryUri(MixinTest.MixinA, I[0]);

可以看到在实际处理中就是将MixinA转化成一个类然后继承MixinBase,所以在MixinA可以用super关键字。

继续,我们将其混入MixinObj,如下:

代码语言:javascript
复制
class MixinObj with MixinBase, MixinA {
  @override
  void init(){
    super.init();
    print("obj");
  }
}

上面执行结果,依次打印“base”,“mixina”,“obj”,因为MixinObj和MixinA都调用了super。

上面代码中如果去掉MixinBase则会编译报错,因为MixinA继承(on)MixinBase,所以它必须在MixinBase的子类上使用,所以MixinObj必须extendswithMixinBase才行。因为MixinBase是class,所以extendswith都可以,但是如果是mixin,则必须with

注意:with后面存在继承(on)关系的时候,父类必须在子类的前面。比如上面MixinBase如果在MixinA后面则会编译报错

on对with的影响

上面提到with的实现其实是一个继承链,with后面的类不会存在继承extends所以不会混乱,但是现在on实际上也是继承,那么有on的情况下继承链又是什么样子的?

我们再加入一个类MixinB:

代码语言:javascript
复制
mixin MixinB on MixinBase {
  @override
  void init(){
    super.init();
    print("mixinb");
  }
}

然后修改MixinObj:

代码语言:javascript
复制
class MixinObj with MixinBase, MixinA, MixinB{
  @override
  void init(){
    super.init();
    print("obj");
  }
}

这里根据上面的原则,MixinBase必须在另外两个的前面,否则报错。

执行后依次打印“base”、“mixinb”、“mixina”、“obj”,通过源码可以看到:

代码语言:javascript
复制
const MixinBase_MixinA$36 = class MixinBase_MixinA extends MixinTest.MixinBase {};
  (MixinBase_MixinA$36.new = function() {
  }).prototype = MixinBase_MixinA$36.prototype;
  dart.applyMixin(MixinBase_MixinA$36, MixinTest.MixinA);
  
  const MixinBase_MixinB$36 = class MixinBase_MixinB extends MixinBase_MixinA$36 {};
  (MixinBase_MixinB$36.new = function() {
  }).prototype = MixinBase_MixinB$36.prototype;
  dart.applyMixin(MixinBase_MixinB$36, MixinTest.MixinB);
  
  MixinTest.MixinObj = class MixinObj extends MixinBase_MixinB$36 {
    init() {
      super.init();
      core.print("obj");
    }
  };

可以看到虽然,MixinA和MixinB都继承(on)MixinBase,但是编译后就打破了这种直接继承的关系,保持之前的继承链逻辑,因为MixinBase一定在MixinA和MixinB前面,所以它还是这两个类的父类,只是不一定是直接父类,所以并不影响。这也是为什么一定要有MixinBase且必须在它俩前的原因

这样就清晰了,如果将MixinB中的super代码去掉,那么就只打印“base”、“mixinb”。如果将with后的MixinB和MixinA换个位置,那么执行顺序就变成了“base”、“mixina”、“mixinb”、“obj”。

总结

通过上面的详细分析,我们可以很清楚的理解了Mixin的机制,它实际上是在编译的时候为类添加了一系列继承关系来实现混入。虽然上面是在Web端实验的,但是在Android端测试也是同样的情况,处理上应该也是类似的,同理其他端如ios应该也一样。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-10-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BennuCTech 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CLI 工具
云开发 CLI 工具(Cloudbase CLI Devtools,CCLID)是云开发官方指定的 CLI 工具,可以帮助开发者快速构建 Serverless 应用。CLI 工具提供能力包括文件储存的管理、云函数的部署、模板项目的创建、HTTP Service、静态网站托管等,您可以专注于编码,无需在平台中切换各类配置。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档