理解 Dart mixin 机制

在 Dart 语言中,咱们常常能够看到对 mixin 关键字的使用,根据字面理解,就是混合的意思。那么,mixin 如何使用,它的使用场景是什么呢。bash

从一个实例提及

咱们假设一个需求,咱们须要用多个对象表示一些 动物, 诸如 狗、鸟、鱼、青蛙。其中编辑器

  1. 狗会跑
  2. 鸟会飞
  3. 鱼会游泳
  4. 青蛙是两栖动物,会跑,而且会游泳

基于以下一些考虑ide

  • 动物特性可能会继续增多,而且一个动物可能具有多种技能
  • 动物种类不少,可是能够归大类。例如 鸟禽、哺乳类

咱们使用以下设计函数

  • 动物继承自 Animal 抽象类
  • 跑、飞、游 抽象为接口

代码以下:布局

abstract class Animal {
}

class Run {
    run() {
        print('run');
    }
}

class Fly {
    fly() {
        print('fly');
    }
}

class Swim {
    swim(){
        print('swim');
    }
}

class Bird extends Animal implements Fly {
    @override
    fly() {
        super.fly();
    }
}

class Dog extends Animal implements Run {
    @override
    run() {
        super.run();
    }
}

class Fish extends Animal implements Swim {
    @override
    swim() {
        super.swim();
    }
}

class Frog extends Animal implements Run,Swim {
    @override
    run() {
        super.run();
    }

    @override
    swim() {
        super.swim();
    }
}
复制代码

这个时候,咱们会发现编辑器报了个错ui

原来这个方法 Dart 会一直认为 super 调用是在调用一个 abstract 的函数,因此咱们这时候须要把这里面集成的函数实现一一实现。spa

这时候问题来了,Frog 和 Fish 都实现了 Swim 接口,这时候 swim 函数的内容咱们须要重复的写 2 遍!设计

回想一下咱们当初在 Android 中写 Java 或者 Kotlin 的时候,其实也有相似问题,同一个 interface 内的 method, 咱们可能须要重写 n 次,很是明显的代码冗余。3d

Java8 和 Kotlin 选择使用接口的 default 实现来解决这个问题:code

interface IXX {
    default void xmethod() {
        /// do sth...
    }
}
复制代码

而 Dart, 选择使用 mixin

修改上面的代码:

abstract class Animal {
}

mixin Run {
    run() {
        print('run');
    }
}

mixin Fly {
    fly() {
        print('fly');
    }
}

mixin Swim {
    swim(){
        print('swim');
    }
}

class Bird extends Animal with Flym {}
class Dog extends Animal with Run {}
class Fish extends Animal with Swim {}
class Frog extends Animal with Run,Swim {}
复制代码

咱们运行以下代码

Bird bird = Bird();
bird.fly();

Frog frog = Frog();
frog.run();
frog.swim();
复制代码

输出以下:

fly
run
swim
复制代码

这里咱们能够意识到,mixin 被混入到了具体的类中,实际也起到了实现具体特性的做用。可是相比实现接口来讲,更加的便捷一点。

这里类的继承关系咱们能够梳理成下图

当函数同样的时候

上述的例子结束了 mixin 的基本用法。咱们能够看到每一个类均可以经过 with 关键字,把 mixin 中定义的特性 “混入” 到本身这里来。可是这时候若是每一个 mixin 的函数名是同样的,会发生什么呢?咱们不妨从新写一个简单的例子。

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 的函数,被调用了。这说明最后一个混入的 mixins 会覆盖前面一个 mixins 的特性。为了验证这个工做流程,咱们稍微修改一下这个例子,给 mixins 的函数加上 super 调用。

mixin MA on S {
  fun() {
    super.fun();
    print('MA');
  }
}
mixin MB on S {
  fun() {
    super.fun();
    print('MB');
  }
}
复制代码

继续执行上面的程序,输出结果以下

A
MA
MB
A
MB
MA
复制代码

第一个 A#fun 为例子。咱们发现实际的调用顺序为 MB -> MA -> A,这里咱们能够看出来 mixin 的工做方式,是具备线性化的。

mixin的线性化

上面的示例,咱们能够画一个图来表示 mixin 是如何线性化的

Dart 中的 mixin 经过建立一个类来实现,该类将 mixin的实现层叠在一个超类之上以建立一个新类 ,它不是“在超类中”,而是在超类的“顶部”。

咱们能够获得如下几个结论:

  1. mixin 能够实现相似多重继承的功能,可是实际上和多重继承又不同。多重继承中相同的函数执行并不会存在 ”父子“ 关系
  2. mixin 能够抽象和重用一系列特性
  3. mixin 实际上实现了一条继承链

最终咱们能够得出一个很重要的结论

声明 mixin 的顺序表明了继承链的继承顺序,声明在后面的 mixin,通常会最早执行

这里再提出一个假设,若是 MA 和 MB 都有一个函数叫 log, 若是在先声明的 mixin 中执行 log 函数,会发生声明事情呢?

代码以下

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 会把后声明的 minxin 的函数覆盖掉。这时候即便咱们从代码逻辑中认为在 MA 中调用了 log 函数,实际上这时候 A 类中的 log 函数已经被 MB 给覆盖了。因此最终,log 函数调用的是 MB 中的 log 函数逻辑。

类型

根据 mixin 的工做原理,咱们彻底能够大胆猜测,最终的子类类型和这个继承链上全部父类和混入的 mixin 的类型均可以匹配上。咱们来验证一下这个猜测:

A a = A();
print(a is A);
print(a is S);
print(a is MA);
print(a is MB);
复制代码

输出结果

true
true
true
true
复制代码

推论彻底正确。

mixin 的使用场景

咱们应该在何时使用 mixin 呢?很简单,在咱们编写 Java 的时候,感受须要实现多个 interface 的时候。

那么,这个和多重继承相比,在某些场景有什么好处吗?答案是有。

在 Flutter 中,framework 的执行依赖多个 Binding,咱们查看最外层 WidgetsFlutterBinding 的定义:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {}
复制代码

WidgetsBindingRendererBinding 中,都有一个叫作 drawFrame 的函数。在 WidgetsBindingdrawFrame中,也有 super.drawFrame() 的调用。

这里 mixin 的优势就体现了出来,咱们能够看到这个逻辑有以下2点

  1. 保证了 widget 等的 drawFrame 先于 render 层的调用,保证了 Flutter 在布局和渲染处理中 widgets -> render 的处理顺序
  2. 保证顺序的同时,Widgets 和 Render 仍然属于 2 个不一样的对象定义,职责分割的很是的清晰。

具体的细节,感兴趣的同窗能够阅读 Flutter 的 flutter package 的源码。

小结

这篇文,我对 Dart 的 mixin 的使用、工做机制、使用场景作了一个大体的总结。mixin 是一个强大的概念,咱们能够跨越类的层次结构重用代码。

文中一些优点和工做机制是个人我的理解。在初次接触 Dart 的这个机制的时候,也须要不少的思惟转变。若是文中我有理解的不对的地方,或者您有不一样的理解。欢迎评论讨论交流。

相关文章
相关标签/搜索