Dart Mixin 介绍

关于 Dart mixin 的一些理解。理解 mixin 概念的关键在于理解中间类。

Mixins are a way of reusing code in multiple class hierarchies

先来看一个简单例子:

1
2
3
4
5
6
7
8
9
10
class Piloted {
int astronauts = 1;
void describeCrew() {
print('Number of astronauts: $astronauts');
}
}

class PilotedCraft extends Spacecraft with Piloted {
// ···
}

PilotedCraft 拥有 astronauts 字段和 describeCrew() 方法。

mixin 是什么?

维基百科中这样定义 mixin:

In object-oriented programming languages, a Mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes.

即,mixin 是另外一个普通类,我们可以在不继承这个类的情况下从这个类”借用”方法和变量。

Support for the mixin keyword was introduced in Dart 2.1. Code in earlier releases usually used abstract class instead.

从这个角度来讲,mixin 不过是 abstract class

Java tries to make up for this by using Interfaces, but that is not as useful or flexible as mixins.

从这个角度来讲,可以认为 mixin 是带实现的接口。

小节

  • mixin 有点类似 abstract class
  • mixin 有点类似 interface
  • 不能继承 mixin
  • 可以使用 mixin, abstract class, class 来作为 mixin

如何使用 mixin?

使用 mixin 的方法很简单:with 关键字后面跟上 mixin 的名字即可。

1
2
3
4
5
6
7
8
9
10
11
class Musician extends Performer with Musical {
// ···
}

class Maestro extends Person
with Musical, Aggressive, Demented {
Maestro(String maestroName) {
name = maestroName;
canConduct = true;
}
}

实现 mixin 的方法同样也很简单:创建一个继承自 Object 的类并且不要声明构造方法。如果想让 mixin 作为普通类使用,使用 class 关键字;如果不想让 mixin 作为普通类使用,使用 mixin 关键字代替 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mixin Musical {
bool canPlayPiano = false;
bool canCompose = false;
bool canConduct = false;

void entertainMe() {
if (canPlayPiano) {
print('Playing piano');
} else if (canConduct) {
print('Waving hands');
} else {
print('Humming to self');
}
}
}

on 的用法

The keyword on is used to restrict our mixin’s use to only classes which either extends or implements the class it is declared on. In order to use the on keyword, you must declare your mixin using the mixin keyword

1
2
3
4
5
6
7
class B {}
mixin Y on B {
void hi() {
print('hi');
}
}
class Q with Y {}

则有如下错误提示:

Error: 'Object' doesn't implement 'B' so it can't be used with 'Y'.

on 关键字限制了 Y 的使用范围:Y 只能用于继承或实现了 B 的类。修复方式是让 Q 继承自 B

1
class Q extends B with Y {}

mixin 解决了什么问题?

mixin 解决了多重继承中的 Deadly Diamond of Death(DDD) 问题。

多重继承问题简单描述。各个类的继承关系如下:

mixin 与继承 -w500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Performer {
abstract void perform();
}

class Dancer extends Performer {
void perform() {}
}

class Singer extends Performer {
void perform() {}
}

class Musician extends Dancer, Singer {
}

问题来了,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是模糊的

来看 mixin 如何解决这个问题。见 Dart for Flutter : Mixins in Dart - Flutter Community - Medium

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Performer {
abstract void perform();
}

mixin Dancer {
void perform() {}
}

mixin Singer {
void perform() {}
}

class Musician extends Performer with Dancer, Singer {
}

现在,当调用 Musician.perform() 时,到底会调用哪个 perform() 方法是确定的。在这里是调用 Singer.perform()

mixin 有一套明确的机制来选择调用哪个方法。

minx 是线性的栈结构 -w500

假设 Musician 类使用多个 mixin (Dancer, Singer)。该类有个方法名为 perform()Musician 类继承自 Performer 类。

  • 首先,将 Performer 类置于栈顶
  • 其次,后声明的 mixin 优先于后声明的 mixin。按顺序将 mixin 置于栈中,在这里分别是 Dancer, Singer
  • 最后,将 Musician 类自己置于栈中。Musician 类中的 perform() 被优先调用

Dart 使用的是单重继承 (Java 也是单重继承,C++ 是多重继承)。多重继承更为强大,但会引起 Deadly Diamond of Death(DDD) 问题。

Java 使用接口(interface)来部分实现多重继承。多重继承的问题是需要在每个类中实现接口(interface),所以并不是一个好的方案。(实际上 Java 已经通过默认方法修复了这个问题)

所以 Dart 中就有了 mixin。

理解 mixin

Mixins in Dart work by creating a new class that layers the implementation of the mixin on top of a superclass to create a new class — it is not “on the side” but “on top” of the superclass, so there is no ambiguity in how to resolve lookups.
Mixins is not a way to get multiple inheritance in the classical sense. Mixins is a way to abstract and reuse a family of operations and state. It is similar to the reuse you get from extending a class, but it is compatible with single-inheritance because it is linear.
StackOverflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
String getMessage() => 'A';
}

class B {
String getMessage() => 'B';
}

class P {
String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
String result = '';

AB ab = AB();
result += ab.getMessage();

BA ba = BA();
result += ba.getMessage();

print(result);
}

以上这段代码输出 BA

从语义上讲以上这段代码等同于:

1
2
3
4
5
6
7
8
9
class PA = P with A;
class PAB = PA with B;

class AB extends PAB {}

class PB = P with B;
class PBA = PB with A;

class BA extends PBA {}

继承结构图是这样的:

mixin 的继承关系 -w400

Since each mixin application creates a new class, it also creates a new interface (because all Dart classes also define interfaces). As described, the new class extends the superclass and includes copies of the mixin class members, but it also implements the mixin class interface.
In most cases, there is no way to refer to that mixin-application class or its interface; the class for Super with Mixin is just an anonymous superclass of the class declared like class C extends Super with Mixin {}. If you name a mixin application like class CSuper = Super with Mixin {}, then you can refer to the mixin application class and its interface, and it will be a sub-type of both Super and Mixin.

理解 mixin 的关键在于它是线性的。

使用场景

  • 在没有共同父类的各个类中共享代码时
  • 在父类中实现某种方法无意义时

源码分析

runApp() 是我们运行应用的入口。这个方法的代码如下:

1
2
3
4
5
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}

这里的的 WidgetsFlutterBinding 联系着 Flutter framework 和 Flutter engine。

A concrete binding for applications based on the Widgets framework.

This is the glue that binds the framework to the Flutter engine.

WidgetsFlutterBinding 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, 
SchedulerBinding, PaintingBinding, SemanticsBinding,
RendererBinding, WidgetsBinding {
}

abstract class BindingBase {
}

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {}

mixin ServicesBinding on BindingBase {}

mixin SchedulerBinding on BindingBase, ServicesBinding {}

mixin PaintingBinding on BindingBase, ServicesBinding {}

mixin SemanticsBinding on BindingBase {}

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {}

abstract class HitTestable {}
  • 定义 mixin。mixin RendererBinding 定义了一个名为 RendererBinding 的 mixin
  • 限制 mixin 的使用范围。mixin RendererBinding on BindingBase, ServicesBinding 限制 RendererBinding 只能用于继承或实现了 BindingBaseServicesBinding 的类上
  • 使用 mixin。class WidgetsFlutterBinding extends BindingBase with RendererBinding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
class WidgetsFlutterBinding extends BindingBase
with
GestureBinding,
ServicesBinding,
SchedulerBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding {}

abstract class BindingBase {
void initInstances() {
print("BindingBase.initInstances()");
}

BindingBase() {
initInstances();
}
}

mixin WidgetsBinding
on
BindingBase,
SchedulerBinding,
GestureBinding,
RendererBinding,
SemanticsBinding {
static WidgetsBinding _instance;

@override
void initInstances() {
super.initInstances();
_instance = this;
print('WidgetsBinding.initInstances() $this');
}
}

mixin GestureBinding on BindingBase implements HitTestable {
static GestureBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('GestureBinding.initInstances() $this');
}
}

mixin ServicesBinding on BindingBase {
static ServicesBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('ServicesBinding.initInstances() $this');
}
}

mixin SchedulerBinding on BindingBase, ServicesBinding {
static SchedulerBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('SchedulerBinding.initInstances() $this');
}
}

mixin PaintingBinding on BindingBase, ServicesBinding {
static PaintingBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('PaintingBinding.initInstances() $this');
}
}

mixin SemanticsBinding on BindingBase {
static SemanticsBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('SemanticsBinding.initInstances() $this');
}
}

mixin RendererBinding
on
BindingBase,
ServicesBinding,
SchedulerBinding,
GestureBinding,
SemanticsBinding,
HitTestable {
static RendererBinding _instance;

void initInstances() {
super.initInstances();
_instance = this;
print('RendererBinding.initInstances() $this');
}
}

abstract class HitTestable {}

main(List<String> args) {
WidgetsFlutterBinding b = WidgetsFlutterBinding();
print('main(): $b');
}

这段代码输出为:

1
2
3
4
5
6
7
8
9
BindingBase.initInstances()
GestureBinding.initInstances() Instance of 'WidgetsFlutterBinding'
ServicesBinding.initInstances() Instance of 'WidgetsFlutterBinding'
SchedulerBinding.initInstances() Instance of 'WidgetsFlutterBinding'
PaintingBinding.initInstances() Instance of 'WidgetsFlutterBinding'
SemanticsBinding.initInstances() Instance of 'WidgetsFlutterBinding'
RendererBinding.initInstances() Instance of 'WidgetsFlutterBinding'
WidgetsBinding.initInstances() Instance of 'WidgetsFlutterBinding'
main(): Instance of 'WidgetsFlutterBinding'

可以这样理解,WidgetsFlutterBinding 并不是继承自 BindingBase,而是继承自如下一个匿名类:

1
2
3
4
5
6
7
8
class BindingBase with
GestureBinding,
ServicesBinding,
SchedulerBinding,
PaintingBinding,
SemanticsBinding,
RendererBinding,
WidgetsBinding

参考