Dart中的extends, with, implements, on关键字详解

语言: CN / TW / HK

Dart中类的类型

Dart是支持基于mixin继承机制的面向对象语言,所有对象都是一个类的实例,而除了 Null以外的所有的类都继承自Object类。 基于mixin的继承意味着尽管每个类(top class Object? 除外)都只有一个超类,一个类的代码可以在其它多个类继承中重复使用。

以上这段是官方文档的说明,在实际使用中,由于mixin的加入,使得Dart中类的使用和其它语言有所不同。Dart中类的类型有三种,分别是: - class:声明一个类,提供具体的成员变量和方法实现。 - abstract class:声明一个抽象类,抽象类将无法被实例化。抽象类常用于声明接口方法、有时也会有具体的方法实现。 - mixin:声明一个Mixin类,与抽象类一样无法被实例化,是一种在多重继承中复用某个类中代码的方法模式,可以声明接口方法或有具体的方法实现。

  1. 每一个类都隐式地定义了一个接口并实现了该接口,这个接口包含所有这个类的成员变量以及这个类所实现的其它接口。
  2. 如果想让抽象类同时可被实例化,可以为其定义工厂构造函数。具体内容可以参考:抽象类的实例化
  3. mixin关键字在Dart 2.1中才被引用支持。早期版本中的代码通常使用 abstract class代替

从上述内容可以看出,mixin是后面才被引入的,与abstract class有些通用的地方,可以理解为abstract class的升级版。它相对于abstract class说,可以同时引入多个Mixin,并且可以通过on关键字来限制使用范围。

类相关关键字的使用

而对上述这些类型的使用,又有extends, with, implements, on这几个关键字: - extends:继承,和其它语言的继承没什么区别。 - with:使用Mixin模式混入一个或者多个Mixin类。 - implements:实现一个或多个接口并实现每个接口定义的API。 - on:限制Mixin的使用范围。

针对这几个关键字的使用,我做了一张表进行总结:

样例说明

针对上面的内容,我举几个例子,可以复制代码到DartPad中进行验证:

类混入类或者抽象类(class with class)

```dart class Animal { String name = "Animal"; } abstract class Flyer { String name = "Flyer"; void fly() => print('$name can fly!'); } abstract class Eater extends Animal { void eat() => print('I can Eat!'); }

// 同时混入class和abstract class abstract class Bird with Animal, Flyer {} class Bird1 with Animal, Flyer {}

// 只支持无任何继承和混入的类,Eater继承自Animal,所以它不支持被混入。 // 报错:The class 'Eater' can't be used as a mixin because it extends a class other than 'Object'. // class Bird with Eater { // }

main() { Bird1().fly(); // Flyer can fly! } ```

类继承抽象类并混入Mixin

```dart class Animal { String name = "Animal"; }

mixin Flyer { String name = "Flyer"; void fly() => print('$name can fly!'); }

abstract class Eater extends Animal { @override String get name => "Eater"; void eat() => print('$name can Eat!'); }

// 类继承抽象类并混入Mixin class Bird extends Eater with Flyer { }

main() { // 因为with(混入)的优先级比extends(继承)更高,所以打印出来的是Flyer而不是Eater Bird().fly(); // Flyer can fly! Bird().eat(); // Flyer can Eat! } ```

类继承抽象类并混入Mixin的同时实现接口

```dart class Biology { void breathe() => print('I can breathe'); }

class Animal { String name = "Animal"; }

// 这里设置实现了Biology接口,但是mixin与abstract class一样并不要求实现接口,声明与实现均可。 // on关键字限制混入Flyer的类必须继承自Animal或它的子类 mixin Flyer on Animal implements Biology { @override String get name => "Flyer"; void fly() => print('$name can fly!'); }

abstract class Eater extends Animal { @override String get name => "Eater"; void eat() => print('$name can Eat!'); }

// 类继承抽象类并混入Mixin的同时实现接口 // 注意关键字的使用顺序,依次是extends -> with -> implements class Bird extends Eater with Flyer implements Biology { // 后面使用了implements Biology,所以子类必须要实现这个类的接口 @override void breathe() => print('Bird can breathe!'); }

main() { // 因为with(混入)的优先级比extends(继承)更高,所以打印出来的是Flyer而不是Eater Bird().fly(); // Flyer can fly! Bird().eat(); // Flyer can Eat! Bird().breathe(); // Bird can breathe! } ```

混入mixin的顺序问题

```dart abstract class Biology { void breathe() => print('I can breathe'); }

mixin Animal on Biology { String name = "Animal"; @override void breathe() { print('$name can breathe!'); super.breathe(); } }

mixin Flyer on Animal { @override String get name => "Flyer"; void fly() => print('$name can fly!'); }

/// mixin的顺序问题: /// with后面的Flyer必须在Animal后面,否则会报错: /// 'Flyer' can't be mixed onto 'Biology' because 'Biology' doesn't implement 'Animal'. class Bird extends Biology with Animal, Flyer { @override void breathe() { print('Bird can breathe!'); super.breathe(); } }

main() { Bird().breathe(); / * 上述代码执行,依次输出: * Bird can breathe! * Flyer can breathe! * I can breathe * / }

`` 这里的顺序问题和运行出来的结果会让人有点费解,但是可以这样理解:Mixin语法糖, 本质还是类继承. 继承可以复用代码, 但多继承会导致代码混乱。Java为了解决多继承的问题, 用了interface, 只有函数声明而没有实现(后面加的default也算语法糖了)。以A with B, C, D为例,实际是A extends D extends C extends B, 所以上面的Animal必须在Flyer前面,否则就变成了Animal extends Flyer`,会出现儿子给爹当爹的混乱问题。

mixin的底层本质只是猜测,并没有查看语言底层源码进行验证.

总结

从上述样例可以看出,三种类结构可以同时存在,关键字的使用有前后顺序:extends -> mixins -> implements。 另外需要注意的是相同方法的优先级问题,这个有两种情况: 1. 同时被extendswithimplements时,混入(with)的优先级比继承(extends)要高,而implements只提供接口,不会被调用。 2. with多个Mixin时,则会调用距离with关键字最远Mixin中的方法。

当然,如果当前使用类重写了该方法,就会优先调用当前类中的方法。

参考资料

by: 星的天空