一、前言
在使用Java
语言设计类之间关系的时候,我们会接触到 组成单元 和 关系连接 这两类概念:
- 组成单元:普通类、
abstract
抽象类,interface
接口。 - 关系连接:
implements
实现,extends
继承。
而在Dart
当中,对于这两类概念进行了增减:
- 组成单元:普通类,
abstract
抽象类、mixin
。 - 关系连接:
implements
实现、extends
继承、with
混入。
最大的不同有两点:
- 去掉了
interface
。 - 增加了混入的概念。
下面我们就来看一下其中涉及到的知识点,前面两节对比一下Java
和Dart
的区别,最后着重介绍混入的概念。
推荐给大家一个网站:dartpad.dartlang.org/ 可以在线运行。
二、组成单元
2.1 普通类
Java
和Dart
的普通类有区别,但是不影响我们设计类之间的关系,因此不再赘述。
2.2 abstract 抽象类
Java
和Dart
的抽象类定义时大体是一样的,我们可以在其中定义变量、普通方法、抽象方法,它和普通类最大的区别就是 抽象类不能实例化。
Java
和Dart
在使用抽象类时有一点不同:Dart
在定义抽象方法时,不需要用abstract
修饰。
abstract class DartAbs {
void absMethod();
}
复制代码
2.3 interface 接口
在Dart
中,没有 interface 关键字。
顺带我们复习一下Java
中abstract
和interface
的一些要点:
- 抽象类和接口都不能被实例化。
- 抽象类要被子类继承,接口要被类实现。
- 接口只能做方法的声明,抽象类可以做方法的声明,也可以做方法的实现。
- 接口里定义的变量只能是公共的静态常量,抽象类中的变量可以是普通变量。
- 抽象类里的抽象方法必须全部被子类实现;接口的接口方法必须全部被子类实现,否则只能为抽象类。
- 抽象类里可以没有抽象方法。
- 如果一个类里有抽象方法,那么这个类只能是抽象类。
- 抽象方法要被实现,所以不能是静态的,也不能是私有的。
- 接口可继承接口,并可多继承接口,但类只能单继承。
三、关系连接
3.1 extends
Java
和Dart
中extends
是一致的,只可以单继承,要注意的点是:
- 子类可以继承父类里面 可见的属性和方法。
- 对于
Java
来说,可见指的是非private
修饰, - 对
Dart
来说,指的是非下划线_
开头。
- 对于
- 子类调用父类的方法,使用
super
关键字。 - 子类不会 继承 父类的构造函数。
class Extends {
void base() {
print('base');
}
void log() {
print('extends');
}
}
class Log extends Extends {
log() {
print('log');
}
}
void main() {
Log().base();
Log().log();
}
复制代码
输出结果:
base
log
复制代码
3.2 implements
implements
与extends
最大的不同就是允许后面接上多个普通或者抽象类,当我们使用B implement A
修饰时,那么A
中的所有的属性和方法都要在A
中实现,无论它原来是抽象方法还是普通方法。
也就是说如果我们只想要A
中的接口定义,而不想要它的实现,那么就试用implements
。
class Implements {
void base() {
print('base');
}
void log() {
print('extends');
}
}
class Log implements Implements {
base() {
print('log#base');
}
log() {
print('log');
}
}
void main() {
Log().base();
Log().log();
}
复制代码
输出结果:
log#base
log
复制代码
四、混入
前面我们介绍的都是Java
中接触过的概念,下面我们来介绍Dart
中特有的概念 - 混入。
4.1 mixin
mixin
用于修饰类,和abstract
类似,该类可以拥有成员变量、普通方法、抽象方法,但是不可以实例化。mixin
一般用于描述一种具有某种功能的组块,而某一对象可以拥有多个不同功能的组块。
(1) 最简单
最简单的mixin
由mixin & with
关键字组成。
举个例子,我们有一种能力是 '绘画',而拥有这种能力的是 ‘教师’,那么实现如下:
mixin DrawFunc {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
class Teacher with DrawFunc {
String what() => "car";
}
void main() {
Teacher().draw();
}
复制代码
(2) 限定类型
我们限定了 '绘画' 这种能力只能够用在 '人类' 上面,示例如下:
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
class Teacher extends Person with DrawFunc {
String what() => "car";
}
void main() {
Teacher().draw();
}
复制代码
当我们在mixin
上使用了on
关键字,那么mixin
只能在那个类的子类上使用,而mixin
可以调用那个类的方法。
(3) 多个类型
在 '绘画' 的基础上,我们增加一种新的能力 '唱歌',示例如下:
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
mixin SingFunc on Person {
void sing() {
print('I can sing');
}
}
class Teacher extends Person with DrawFunc, SingFunc {
String what() => "car";
}
void main() {
Teacher().draw();
Teacher().sing();
}
复制代码
(4) on 的一种复杂变形
关于on
还有一种复杂的变形,我们在 '唱歌' 上增加一条约束,要求它必须是在DrawFunc
之上:
mixin SingFunc on Person, DrawFunc {
void sing() {
print('I can sing');
}
}
复制代码
那么这时候,虽然Teacher
没有extends DrawFunc
,但是如下的代码仍然可以编译通过:
class Teacher extends Person with DrawFunc, SingFunc {
String what() => "car";
}
复制代码
而我们交换一下DrawFunc
和SingFunc
的顺序就不行了:
class Teacher extends Person with SingFunc, DrawFunc {
String what() => "car";
}
复制代码
提示信息是:
Error compiling to JavaScript:
main.dart:22:7:
Error: 'Person' doesn't implement 'DrawFunc' so it can't be used with 'SingFunc'.
- 'Person' is from 'main.dart'.
- 'DrawFunc' is from 'main.dart'.
- 'SingFunc' is from 'main.dart'.
class Teacher extends Person with SingFunc, DrawFunc {
^
Error: Compilation failed.
复制代码
结论:要满足
on
的要求,除了使用extends
之外,还可以在with
列表中,在它之前进行声明。在Flutter
的WidgetsFlutterBinding
中,就涉及到了这一点的运用。
abstract class BindingBase {}
mixin ServicesBinding on BindingBase {}
mixin SchedulerBinding on BindingBase, ServicesBinding {}
mixin RendererBinding on BindingBase, ServicesBinding {}
class WidgetsFlutterBinding extends BindingBase with ServicesBinding, SchedulerBinding, RendererBinding {}
复制代码
(5) Tips
在这上面,我们接触了几个新的概念mixin, on, with
:
mixin
:定义了组块。on
:限定了使用mixin
组块的宿主必须要继承于某个特定的类;在mixin
中可以访问到该特定类的成员和方法。with
:负责组合组块,而with
后面跟的类并不一定需要是mixin
的,abstract class
和普通类都是可以的,这一点需要注意,例如下面这样:
class Person {}
mixin DrawFunc on Person {
String content = '..';
String what();
void draw() {
print('I can draw ${what()}');
}
}
mixin SingFunc on Person {
void sing() {
print('I can sing');
}
}
abstract class DanceFunc {
void dance() {
print('I can dance');
}
}
class Teacher extends Person with DrawFunc, SingFunc, DanceFunc {
String what() => "car";
}
void main() {
Teacher().draw();
Teacher().sing();
Teacher().dance();
}
复制代码
4.2 冲突
如果同时存在extends, with
,并且它们都定义了相同的方法名,那么结果如何呢?我们来看下面的例子:
class Extends {
void log() {
print('extends');
}
}
mixin Mixins {
void log() {
print('mixin');
}
}
mixin Mixins2 {
void log() {
print('mixin2');
}
}
class Log extends Extends with Mixins, Mixins2 {}
void main() {
Log().log();
}
复制代码
输出结果:
mixin2
复制代码
结论
with
修饰的会覆盖extends
中修饰的同名方法。with
列表中后一个的会覆盖之前的。
再来看一下加上了implements
的情况。
class Extends {
void log() {
print('extends');
}
}
mixin Mixins {
void log() {
print('mixin');
}
}
mixin Mixins2 {
void log() {
print('mixin2');
}
}
class Implements {
void log() {
print('implements');
}
}
class Log extends Extends with Mixins, Mixins2 implements Implements {}
void main() {
Log().log();
}
复制代码
输出结果为:
mixin2
复制代码
这里我们发现了一个奇怪的现象:虽然我们加上了implements
,但是Dart
居然没让我们实现Implements.log()
方法!
这是因为在这种情况下,它识别到我们从with
和extends
中获得了log()
方法的能力,因此调用的是Mixins2.log()
。
假如我们对Implements#log
方法进行实现:
class Log extends Extends with Mixins, Mixins2 implements Implements {
void log() {
print("implements log");
}
}
复制代码
输出的结果为:
implements log
复制代码
五、小结
梳理下来,有几点感想:
- 之前我们设计中用到了
interface
的部分,可以采用只带有抽象方法的abstract class
替换,并继续使用implements
关键字。 - 理解
mixin
的概念,我是将它理解为一个个的功能组块:哪些宿主需要哪些功能,我就with
到上去。 on
关键字一方面是为了限制组块的应用场景,也可以为多个组块提供公共的基础功能。