【Flutter】一文读懂混入类Mixin
【Flutter】一文读懂混入类Mixin
基本介绍
Mixin是一种有利于代码复用,又避免了多继承的解决方案。
Mixin 是面向对象程序设计语言中的类,提供了方法的实现,其他类可以访问 Mixin 类的方法而不必成为其子类;Mixin 为使用它的 Class 类提供额外的功能,但自身却不单独使用(不能单独生成实例对象,属于抽象类),Mixin 类通常作为功能模块使用,在需要该功能时“混入”,而且不会使类的关系变得复杂;
Mixin 有利于代码复用性同时又避免了多继承的复杂性,使用 Mixin 享有单一继承的单纯性和多重继承的共有性,interface 接口与 Mixin 相同的地方是都可以多继承,不同的地方在于 Mixin 是可以实现的;
对应关系
继承 | 混入 | 接口 | |
---|---|---|---|
关键字 | extends | with | implements |
对应数量 | 1:1 | 1:n | 1:n |
代码设置顺序 | 前 | 中 | 后 |
耦合度 | 高 | 中 | 低 |
举例学习
首先,众所周知...Java只能单继承,
假如我们面临下面这一种需求:
,我们需要用多个对象表示一些 动物, 诸如 狗、鸟、鱼、青蛙。其中
- 狗会跑
- 鸟会飞
- 鱼会游泳
- 青蛙是两栖动物,会跑,并且会游泳
基于如下一些考虑
- 动物特性可能会继续增多,并且一个动物可能具备多种技能
- 动物种类很多,但是可以归大类。例如 鸟禽、哺乳类
我们使用如下设计
- 动物继承自 Animal 抽象类
- 跑、飞、游 抽象为接口
我们按照上面的需求...让copilotX帮我写一个类的实现...
可以看到AI生成的代码还是很给力的,但是我们可以发现,Frog和Dog都实现了Run的抽象方法。
假如我们现在尝试让代码复用率变高,让Run,Fly,Swim作为实现,看看会发生什么...
可以看到,我们的Copilit告诉了我们问题
原来这个写法 Dart 会一直认为 super
调用是在调用一个 abstract 的函数,所以我们这时候需要把这里面集成的函数实现一一实现。
这时候问题来了,Frog 和 Fish 都实现了 Swim 接口,这时候 swim 函数的内容我们需要重复的写 2 遍!
(当然我们指的就是前面AI生成的代码)
当然,作为一篇Mixin教学,我们对这个结果肯定是不满意的...
现在,我们完全没学过类似Java的default关键字的知识点...我们只是个渴望dart的小白...
选择使用mixin
,重新定义Run,Fly,Swim方法,子类也不再是实现接口而是混入。
可以看到,mixin被混入到了类中,也实现了对应“抽象类”的特性。
这里类的继承关系我们可以梳理成下图
这里也可以增加一个新的理解:mixin并不是对子类的拓展,而是对父类的拓展
mixin,class,interface的异同
mixin也可以使用class关键字定义,也可以当做普通class一样使用。
mixin可以使用with定义,这样定义的mixin就只能通过with关键字引用了。
Dart是没有interface这种东西的,但并不意味着这门语言没有接口,事实上,Dart任何一个类都是接口,你可以实现任何一个类,只需要重写那个类里面的所有具体方法。
所以,Dart中的任何一个class,既是类,又是接口,也可以当作mixin使用
这意味着:
-
混入类可以持有成员变量,也可以声明和实现成员方法。而混入一个类,就可以访问其中的成员属性和方法,这点和
继承
很像 -
一个类可以混入若干个类,通过
,
分隔开,这个功能和接口
类似,但是和接口不同的是:混入类本身可以对方法进行实现,而接口内必须是抽象方法 -
混入类支持抽象方法,但是这要求了派生类必须实现抽象方法,这一点又和
抽象类
很像。mixin PaintAble{ late Paint painter; void paint(){ print("=====$runtimeType paint===="); } void init(); } class Shape with MoveAble,PaintAble{ @override void init() { painter = Paint(); } } // 这里的Shape作为派生类,必须实现PaintAble中声明的抽象方法init
mixin的限制
可以看到,在混入了之后,就可以使用mixin的所有方法了,但是有时我们并不希望所有类都可以使用一些方法。比如我在Dog类中with一个Fly,这就意味着我们的狗可以飞了!
所以...为了守护自然界的秩序,mixin提供了一种限制:on 关键字
规定了:on后面衔接的类和它的子类才可以被混入
除此之外,on还可以限定mixin之间的继承关系,参考下一小节
mixin Fly on Bird{
void fly(){
print('只有鸟类可以混入Fly')
}
}
除了类的限制外,mixin本身就是一种限制。
因为刚刚提到,dart中的任何一个类都可以被混入,而使用mixin声明的类,需要使用with关键字才可以替换。
除此之外的一点小改动...
细心的你可能会发现,在我们的样例中直接这样修改是没办法通过编译的。这是因为上面那句话:
mixin并不是对子类的拓展,而是对父类的拓展,也就是说,我们在代码中,相当于将Animal拓展了一个Fly功能,而我们规定了,Fly方法只能被Bird及Bird的子类使用。Animal并不属于Bird的子类(反倒是他的父类),所以会报错。
继承的二义性问题
先说说什么是二义性问题:
(内容参考如下文章:C++多继承中的二义性问题_继承的二义性-CSDN博客)
在C++中,派生类继承基类,对基类成员的访问应该是确定的、唯一的,但是常常会有以下情况导致访问不一致,产生二义性。
1.在继承时,基类之间、或基类与派生类之间发生成员同名时,将出现对成员访问的不确定性——同名二义性。
2.当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生另一种不确定性——路径二义性。
而在接口
中,牺牲了接口的普通成员
和方法实现
,最终才解决二义性问题,最终能够支持多实现。
在混入类
中,不能拥有构造方法,也就是说不能实例化。这一点跟抽象类
、接口
是一样的。
看如下的实例:
class S {
fun() => print('A');
}
mixin MA {
fun() => print('MA');
}
mixin MB {
fun() => print('MB');
}
class A extends S with MA, MB {}
class B extends S with MB, MA {}
main() {
A a = A();
a.fun();
B b = B();
b.fun();
}
运行代码,得到如下的结果:
MB
MA
我们可以得出结论:最后一个混入的mixin,会覆盖前面的mixin的特性
为了验证这个结论,我们给mixin加入super调用和mixin的继承关系
mixin MA on S {
fun() {
super.fun();
print('MA');
}
}
mixin MB on S {
fun() {
super.fun();
print('MB');
}
}
运行代码,得到如下结果:
A
MA
MB
A
MB
MA
这里我们得到mixin的工作方式:线性化
Mixin的线性化
Dart 中的 mixin
通过创建一个类来实现,该类将 mixin
的实现层叠在一个超类之上以创建一个新类 ,它不是“在超类中”,而是在超类的“顶部”。
我们可以得到以下几个结论:
mixin
可以实现类似多重继承的功能,但是实际上和多重继承又不一样。多重继承中相同的函数执行并不会存在 ”父子“ 关系mixin
可以抽象和重用一系列特性mixin
实际上实现了一条继承链- A is S,A is MA,A is MB。
最终我们可以得出一个很重要的结论
声明 mixin 的顺序代表了继承链的继承顺序,声明在后面的 mixin,一般会最先执行
线性化的覆盖实例
参考如下代码
class S {
fun()=>print('A');
}
mixin MA on S {
fun() {
super.fun();
log();
print('MA');
}
log() {
print('log MA');
}
}
mixin MB on S {
fun() {
super.fun();
print('MB');
}
log() {
print('log MB');
}
}
class A extends S with MA,MB {}
A a = A();
a.fun();
按照我们常见的思维方式,可能会认为得到的结论为:
A
log MA
MA
MB
但事实上,得到的输出结果为:
A
log MB
MA
MB
因为按照上面的工作原理,在mixin
的继承链建立了之后,最后声明的mixin会把前面声明的mixin函数覆盖掉,所以即使我们此时在MA函数中调用了log
,而事实上MA里面的log
函数被MB覆盖了,最后调用的是MB。
小结论:调用了super就可以从前往后看执行顺序,如果存在函数内同名调用函数的情况要从后往前看
混入类之间的继承关系
另外,两个混入类间可以通过 on
关键字产生类似于 继承
的关系
mixin A{
int i = 5;
}
mixin B on A{
int j = 6;
void show(){
print(i);
print(j);
}
}
class C with A,B{
}
main(){
C c = new C();
c.show();
}
可以看到,B中可以通过on A来访问A内的成员变量。
同时C with A,B
不可以调换顺序,否则编译器会报错。这也符合我们之前说的线性关系,因为“B继承A”,所以,只有“B覆盖了A”这种线性关系才是可以被接受的。
extends,mixin,implements的执行顺序
class Ex{
Ex(){
print('extends constructor');
}
void show(){
print('extends show');
}
}
// dart 没有 interface 关键字,但是可以使用 abstract class 来实现接口的功能
abstract class It{
void show();
}
mixin mx1 on Ex{
void show(){
super.show();
print('mx1show');
}
}
mixin mx2 on Ex{
void show(){
super.show();
print('mx2show');
}
}
class C12 extends Ex with mx1,mx2 implements It{
@override
void show() {
super.show();
print('it show');
}
}
class C21 extends Ex with mx2,mx1 implements It{
@override
void show() {
super.show();
print('it show');
}
}
void main(){
C12 c12 = new C12();
c12.show();
C21 c21 = new C21();
c21.show();
}
执行结果:
extends constructor
extends show
mx1show
mx2show
it show
extends constructor
extends show
mx2show
mx1show
it show
结论:执行顺序是 extends 继承优先执行,之后是 with 混入,最后是 implements 接口重载;
Flutter的runAPP
接下来我们回到Flutter,看一下runAPP()的形式
WidgetsFlutterBinding.ensureInitialized 方法如下:
WidgetsFlutterBinding 混合结构如下:
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
BindingBase 及构造函数如下:
其执行了 initInstances 和 initServiceExtensions 方法。看下面混合的顺序:
从后到前依次执行其 initInstances 和 initServiceExtensions(如果有) 方法,由于 initInstances 和 initServiceExtensions 方法中首先执行 super.initInstances() 和 super.initServiceExtensions() ,所以最后执行的顺序为:BindingBase -> GestureBinding -> SchedulerBinding -> ServicesBinding -> PaintingBinding -> SemanticsBinding -> RendererBindinsg -> WidgetsBinding 。
而在WidgetsBinding和RendererBinding中,都有一个叫做drawFrame的函数,而Widget的drawFrame调用了super.drawFrame,同时Widgets on Renderer
这里反应的逻辑有如下两点:
- 保证widget等的drawFrame能够先于render调用。保证了flutter在布局和渲染处理时 widgets->render
- 保证了顺序的同时,两者仍然各个负责自己的部分
参考文章
Flutter 语法进阶 | 深入理解混入类 mixin - 掘金 (juejin.cn)
彻底理解 Dart mixin 机制 - 掘金 (juejin.cn)
Flutter 必知必会系列 —— mixin 和 BindingBase 的巧妙配合 - 掘金 (juejin.cn)
【Flutter 专题】103 初识 Flutter Mixin - 掘金 (juejin.cn)
跟我学flutter:我们来举个例子通俗易懂讲解dart 中的 mixin - 掘金 (juejin.cn)
Flutter 中不得不会的 mixin - 老孟Flutter - 博客园 (cnblogs.com)