Flutter基础:理解Dart的Mixin继承机制

Dart语言集合了现代编程语言的众多优点,Mixin继承机制也是其一。但针对Java程序员来说,可能不是一下子能理解的,比如我第一次看到的时候,也迷迷糊糊了半天——这是啥玩意???

要说Mixin,可能写成MixIn会更好理解,翻译回来就是混入,当然你执意说这是一种“迷信”继承机制,那也没辙。

下面将从一个实际情景入手,对比Java和Dart的实现,以便更好理解Dart的mixin。

场景

我们先来描绘这么一个职业关系图:
职业关系图

从图中可以梳理出以下关系:

  • 工程师类目,有软件工程师和建筑工程师师等,他们共同点都是工程师
  • 教师类目有美术教师,IT教师等,他们共同点都是教师

而以上的工程师、教师都是社会的工作者。

那么接下来我们分别使用Java和Dart来实现这个关系类。

注意:接下来的这篇文章中为了方便表达意义,将所有类写在一个文件里面,请暂时忽略代码不规范细节。并且Dart在本文不会使用语法糖写法。

Java版本实现

从层级出发,可以写出Java版本实现如下:

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
//工作者
abstract class Worker {
public abstract void doWork();//工作者需要工作
}

//工程师
class Engineer extends Worker {
@Override
public void doWork() {
System.out.println("工程师在工作");
}
}

//教师
class Teacher extends Worker {
@Override
public void doWork() {
System.out.println("教师在教学");
}
}

//软件工程师
class SoftwareEngineer extends Engineer {

}

//建筑工程师
class BuildingEngineer extends Engineer {

}

//美术教师
class ArtTeacher extends Teacher {

}

//IT教师
class ITTeacher extends Teacher {

}

Dart版本实现

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
//工作者
abstract class Worker {
void doWork();//工作者需要工作
}

//工程师
class Engineer extends Worker {
void doWork() {
print('工程师在工作');
}
}

//教师
class Teacher extends Worker {
void doWork() {
print('教师在教学');
}
}

//软件工程师
class SoftwareEngineer extends Engineer {

}

//建筑工程师
class BuildingEngineer extends Engineer {

}

//美术教师
class ArtTeacher extends Teacher {

}

//IT教师
class ITTeacher extends Teacher {

}

从上面实现可以看出,两个实现并没什么卵区别好咩。。。。。。

嗯,目前来说确实是这样的,因为Dart也是单继承。

因为上面的场景是在是too young too simple了,下面开始扩展一些场景。

场景扩展

还是刚刚那张关系图,我们开始思考这些职业他们都具有什么能力。

于是我给这些职业虚拟了以下能力:

职业 能力
软件工程师 软件设计、修电脑
建筑工程师 手绘
美术教师 手绘、书法
IT教师 修电脑

他们的关系图如下:

职业能力关系图

通过图形或表格可以看出,软件工程师和IT教师都具备修电脑的能力,建筑工程师和美术教师都具备手绘的能力,但是这些能力都是他们特有的,不是工程师或者教师具备的能力,所以不能在他们的父类中实现。

那么这个时候我们就考虑到一个东西——接口。

以软件工程师和IT教师为例:

他们都具备修电脑的能力:

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
interface CanFixComputer {
void fixComputer();
}

interface CanDesignSoftware {
void designSoftware();
}

//软件工程师
class SoftwareEngineer extends Engineer implements CanFixComputer, CanDesignSoftware {

@Override
public void fixComputer() {
System.out.println("修电脑");
}

@Override
public void designSoftware() {
System.out.println("设计软件");
}
}

//IT教师
class ITTeacher extends Teacher implements CanFixComputer {

@Override
public void fixComputer() {
System.out.println("修电脑");
}
}

而这个在Dart里面的实现如下:

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
abstract class CanFixComputer {
void fixComputer();
}

abstract class CanDesignSoftware {
void designSoftware();
}

//软件工程师
class SoftwareEngineer extends Engineer implements CanFixComputer, CanDesignSoftware {
void fixComputer() {
print('修电脑');
}

void designSoftware() {
print('设计软件');
}
}

//IT教师
class ITTeacher extends Teacher implements CanFixComputer {
void fixComputer() {
print('修电脑');
}
}

前面系列文章中也提到过,Dart是没有interface这种东西的,但并不以为着这门语言没有接口,事实上,Dart任何一个类都是接口,你可以实现任何一个类,只需要重写那个类里面的所有具体方法。

所以,这么看来,Dart实现和java只不过是语法上稍微不一样,但本质上还是没啥卵区别啊?

是的,你大可使用Java的编程习惯来写Dart,就像你刚从C转到Java的时候,用面向过程的思想去编程,程序也能跑起来,看似能work的,但如果你对代码要求稍微高一点,又或者希望了解当前你在写的这门语言的设计思想,那就应该去了解如何更高效的使用一门语言。

还是上面的例子,在Dart里面,如果运用Mixin思想,那么他的呈现应该是怎样的?

我们把修电脑和软件设计能力封装起来,同时使用factory关键字结合_权限符避免外部实例化和扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
abstract class CanFixComputer {
factory CanFixComputer._() {
return null;
}

void fixComputer() {
print('修电脑');
}
}

abstract class CanDesignSoftware {
factory CanDesignSoftware._() {
return null;
}

void designSoftware() {
print('设计软件');
}
}

那么Mixin是怎么个Mix法,又是怎么个in法的呢?

如下面例子所示:

1
2
3
4
5
6
7
8
9
10
//软件工程师
class SoftwareEngineer extends Engineer
with CanFixComputer, CanDesignSoftware {

}

//IT教师
class ITTeacher extends Teacher with CanFixComputer {

}

使用上是这样的:

1
2
3
4
5
6
7
8
9
main() {
ITTeacher itTeacher = new ITTeacher();
itTeacher.doWork();
itTeacher.fixComputer();
SoftwareEngineer softwareEngineer = new SoftwareEngineer();
softwareEngineer.doWork();
softwareEngineer.fixComputer();
softwareEngineer.designSoftware();
}

可以看到,这里不再用implements,更不是extends,而是with

而且,每个具有某项特性的类不再需要具体去实现同样的功能,接口是没法实现功能的,而通过继承的方式虽然能实现功能,但已经有父类,同时不是一个父类,又不能多继承,所以这个时候,Dart的Mixin机制就比Java的接口会高效,开发上层的只需要关心当前需要什么特性,而开发功能模块的关心具体要实现什么功能。

顺序的理解

既然是with,那应该也会有顺序的区别,
思考一个问题:如果同时with两个类,但两个类中有同样的一个方法的不同实现,那么这个时候应该使用的是哪一个类的方法?

下面以一个简单的Demo来说明这个问题:

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
class First {
void doPrint() {
print('First');
}
}

class Second {
void doPrint() {
print('Second');
}
}

class Father {
void doPrint() {
print('Father');
}
}

class Son1 extends Father with First,Second {
void doPrint() {
print('Son1');
}
}

class Son2 extends Father with First implements Second {
void doPrint() {
print('Son2');
}
}

main() {
Son1 son1 = new Son1();
son1.doPrint();
Son2 son2 = new Son2();
son2.doPrint();
}

那么这个程序运行后,将会在控制台输出如下:

1
2
Son1
Son2

可以看到,无论是extends、implements还是mixin,优先级最高的是在具体类中的方法。

我们稍微改一下上面的例子:

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
class First {
void doPrint() {
print('First');
}
}

class Second {
void doPrint() {
print('Second');
}
}

class Father {
void doPrint() {
print('Father');
}
}

class Son1 extends Father with First,Second {

}

class Son2 extends Father with First implements Second {

}

main() {
Son1 son1 = new Son1();
son1.doPrint();
Son2 son2 = new Son2();
son2.doPrint();
}

这个时候控制台输出如下:

1
2
Second
First

可以看到,其实在Son2中implements只是说要实现他的doPrint()方法,这个时候其实具体实现是First中Mixin了具体实现。
而Mixin的具体顺序也是可以从代码倒过来看的,最后mixin的优先级是最高的。

以Son1为例,他的关系是这样的:

son1的关系

总结

上面例子下来应该能理解了Dart中的Mixin的继承机制,当我们的继承父类不是同一个的,同时子类里面需要实现同样的功能时,Mixin显得尤为重要。

但需要注意的是Mixin目前也还有一些限制,引用Gilad Bracha写的Mixins in Dart里的一段话是这么说的:

Dart 1.13 and greater supports mixins that can extend from classes other than Object, and can call super.method(). This support is only available by default in the Dart VM and in Analyzer behind a flag. More specifically, it is behind the –supermixin flag in the command-line analyzer. It is also available in the analysis server, behind a client-configurable option. For example, in Atom use the analysis.updateOptions request to set the enableSuperMixins option to true. Dart2js and DDC do not support this yet.

简单来说就是在Dart 1.13或者更高版本已经支持继承Object外的类并使用Mixin,而且可以调用super.method(),这项支持仅仅在Dart VM中默认开启,Dart2js和DDC中是还不支持的。

但据我最新了解在Flutter的Dart2上面已经可以支持直接调用super()并且从Object以外的类拓展了。

所以我们作为Flutter开发者,其实不用担心mixin会怎么大改动,只会增强,不会阉割,放心大胆使用就是了。

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/ae2ce64/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu.CN
支付宝打赏