随着Dart
学习的深入,发现了一个比较棘手的语法——mixin
。它对于Java
开发人员来说,是一个全新的概念,但也是深入学习Dart
无法绕过的一个槛。
那么mixin
到底是什么尼???下面来看维基百科对它的定义。
mixin
是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin
类的方法、变量而不必成为其子类。
简而言之,mixins
是普通的类,我们可以从中扩展方法(或变量)而不扩展类。
首先我们来看一个应用场景。
在上图中,有一个类——Animal
,它有三个子类——Mammal
、Bird
及Fish
,而这三个类也有其对应的子类。
下面分别通过Java
及Dart
来实现上面这些类及其继承关系。
Java
代码
//Java代码
public class Animal {...}
public class Mammal extends Animal {...}
public class Bird extends Animal {...}
public class Fish extends Animal {...}
public class Dolphin extends Mammal {...}
public class Bat extends Mammal {...}
public class Cat extends Mammal {...}
public class Dove extends Bird {...}
public class Duck extends Bird {...}
public class Shark extends Fish {...}
public class FlyingFish extends Fish {...}
Dart
代码
//Dart代码
class Animal {...}
class Mammal extends Animal {...}
class Bird extends Animal {...}
class Fish extends Animal {...}
class Dolphin extends Mammal {...}
class Bat extends Mammal {...}
class Cat extends Mammal {...}
class Dove extends Bird {...}
class Duck extends Bird {...}
class Shark extends Fish {...}
class FlyingFish extends Fish {...}
代码几乎一摸一样,下面就根据上图来分别给这些类添加行为——Walk
,Swim
及Flying
。由于这些行为并不是所有类通用的,所以不能将这些行为放在父类。但如果把这三个行为分别放在三个类中,然后让其他类来继承这三个类,也就可以解决上述问题。但这样就是多继承,而Java
及Dart
又不支持多继承。所以这时候凸显出接口的重要性,通过接口来实现上述行为。代码如下:
//行走行为
public interface Walk {
void walk();
}
//游泳行为
public interface Swim {
void swim();
}
//飞翔行为
public interface Flying {
void flying();
}
//海豚可以游泳
public class Dolphin extends Mammal implements Swim {
@Override
public void swim() {...}
}
//蝙蝠可以飞、行走
public class Bat extends Mammal implements Flying,Walk {
@Override
public void walk() {...}
@Override
public void flying() {...}
}
//猫可以行走
public class Cat extends Mammal implements Walk {
@Override
public void walk() {...}
}
//鸽子可以行走、飞
public class Dove extends Bird implements Walk,Flying {
@Override
public void walk() {...}
@Override
public void flying() {...}
}
//鸭子可以行走、飞及游泳
public class Duck extends Bird implements Walk,Flying,Swim {
@Override
public void swim() {...}
@Override
public void walk() {...}
@Override
public void flying() {...}
}
//鲨鱼可以游泳
public class Shark extends Fish implements Swim {
@Override
public void swim() {...}
}
//飞鱼可以游泳、飞
public class FlyingFish extends Fish implements Swim,Flying {
@Override
public void swim() {...}
@Override
public void flying() {...}
}
在Java
中通过接口给类添加了行为。下面就开始在Dart
中给类添加行为,虽然Dart
中没有interface
关键字,但Dart
中是有接口的概念且任意类都可以作为接口。代码如下:
//行走行为
abstract class Walk{
walk();
}
//游泳行为
abstract class Swim{
swim();
}
//飞翔行为
abstract class FLying {
flying();
}
//海豚可以游泳
class Dolphin extends Mammal implements Swim {
@Override
public void swim() {...}
}
//蝙蝠可以飞、行走
class Bat extends Mammal implements Flying,Walk {
@Override
void walk() {...}
@Override
void flying() {...}
}
//猫可以行走
class Cat extends Mammal implements Walk {
@Override
void walk() {...}
}
//鸽子可以行走、飞
class Dove extends Bird implements Walk,Flying {
@Override
void walk() {...}
@Override
void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird implements Walk,Flying,Swim {
@Override
void swim() {...}
@Override
void walk() {...}
@Override
void flying() {...}
}
//鲨鱼可以游泳
class Shark extends Fish implements Swim {
@Override
void swim() {...}
}
//飞鱼可以游泳、飞
class FlyingFish extends Fish implements Swim,Flying {
@Override
void swim() {...}
@Override
void flying() {...}
}
代码几乎与Java
实现一摸一样。但在Dart
中,我们还可以用Mixin
来实现上述需求。通过Mixin
将上面的一些行为加入到各自对应的类中。下面来代码实现:
//行走
mixin Walker{
void walk(){...}
}
//游泳行为
mixin Swim{
void swim(){...}
}
//飞翔行为,由于这个是抽象方法,所以必须在要实现,不能调用super.flying()
mixin Flying {
void flying();
}
//海豚可以游泳
class Dolphin extends Mammal with Swim{
@override
void swim() {
super.swim();
}
}
//蝙蝠可以飞、行走
class Bat extends Mammal with Flying,Walk{
@override
void flying() {...}
//覆盖Walk类中的walk方法
@override
void walk() {
super.walk();
}
}
//猫可以行走,这里没有重写Walk中的方法
class Cat extends Mammal with Walk{}
//鸽子可以行走、飞
class Dove extends Bird with Flying,Walk{
@override
void flying() {...}
}
//鸭子可以行走、飞及游泳
class Duck extends Bird with Walk,Flying,Swim{
@override
void flying() {...}
@override
void walk() {...}
}
//鲨鱼可以游泳
class Shark extends Fish with Swim{...}
//飞鱼可以飞及游泳
class FlyingFish extends Fish with Flying,Swim{
@override
void flying() {...}
}
咋一看,这不就是将implement
替换成了with
,abstract class
替换成了mixin
嘛,也太简单了。但仔细一看,我们发现mixin
里有方法的具体实现,这样可以避免接口的方法必须在子类实现从而导致的代码冗余(Java 8
通过关键字default
也可以做到这一点)问题。简而言之,mixin
相对于接口能够更好的避免代码冗余,使代码更加集中。
其实笔者之所以学习mixin
,是因为在flutter
源码中大量使用了mixin
,而如果不了解mixin
的话,就无法分析flutter
源码。哈哈哈…
在上面的示例中,我们发现with
关键字后有多个类。那么这里就产生了一个问题——如果with
后的多个类中有相同的方法,那么当调用该方法时,会调用哪个类里的方法尼?由于距离with
关键字越远的类会重写前面类中的相同方法,因此分为以下两种情况
with
关键字最远类中的方法。下面来看一个示例。
//BindingBase
abstract class BindingBase {
void initInstances() {
print("BindingBase——initInstances");
}
}
//GestureBinding
mixin GestureBinding on BindingBase{
@override
void initInstances() {
print("GestureBinding——initInstances");
super.initInstances();
}
}
//RendererBinding
mixin RendererBinding{
@override
void initInstances() {
print("RendererBinding——initInstances");
super.initInstances();
}
}
// WidgetsBinding
mixin WidgetsBinding on BindingBase
{
@override
void initInstances() {
print("WidgetsBinding——initInstances");
super.initInstances();
}
}
//WidgetsFlutterBinding
class WidgetsFlutterBinding extends BindingBase
with GestureBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
return WidgetsFlutterBinding();
}
}
//main
main(List arguments) {
var binding = WidgetsFlutterBinding();
binding.initInstances();
}
WidgetsFlutterBinding
中并没有重写initInstances
方法,那么就以最右边重写该方法的类——WidgetsBinding
为主。那么结果应该如下。
但其实真实结果其实如下。
这是为什么尼?仔细一点就可以发现,我们在WidgetsBinding
、RendererBinding
及GestureBinding
中都调用了父类的initInstances
方法,也因此会一级一级往上调用。如果我们取消该句代码,则会终止这种调用方式。
比如做如下修改。
mixin WidgetsBinding on BindingBase
{
@override
void initInstances() {
print("WidgetsBinding——initInstances");
}
}
其他代码保持不变,这样就是我们前面预想的结果了。
依次类推…
最后在用Java
代码展示一下上面的继承关系。
public class BindingBase {
void initInstances() {
System.out.printf("BindingBase——initInstances");
}
}
public class GestureBinding extends BindingBase {
@Override
void initInstances() {
// super.initInstances();
}
}
public class RendererBinding extends GestureBinding {
@Override
void initInstances() {
// super.initInstances();
}
}
public class WidgetsBinding extends RendererBinding {
@Override
void initInstances() {
// super.initInstances();
}
}
public class WidgetsFlutterBinding extends WidgetsBinding {
}
主要是说明一下在学习mixin
过程中遇到的几个问题。
mixin
类要么是直接继承Object
,要么是直接或间接继承extends
关键字后的类。在前面的示例上做一些修改,如下。
//一个新类
abstract class Binding {
void initInstances() {
print("Binding——initInstances");
}
}
//让GestureBinding类继承自Binding
mixin GestureBinding on Binding {
@Override
void initInstances() {
// super.initInstances();
}
}
这时候我们就会发现代码报错,出现了如下警告。
当我们再次让GestureBinding
继承自BindingBase
时,上面错误就消失了。所以我们要清楚mixin
到底是继承那个类,否则就可能会出现上述错误。
如果类A实现了接口C,类B继承了接口C,那么类B一定得在类A的后面。
//接口
abstract class HitTestable {
void hitTest(String msg);
}
//实现接口HitTestable
mixin GestureBinding on BindingBase implements HitTestable{
@override
void initInstances() {
print("GestureBinding——initInstances");
super.initInstances();
}
@override
void hitTest(String msg) {
print("GestureBinding——hitTest:${msg}");
}
}
mixin RendererBinding on BindingBase,GestureBinding,HitTestable{
@override
void hitTest(String msg) {
print("RendererBinding——hitTest:${msg}");
super.hitTest(msg);
}
}
那么如果想要在with
后加上类GestureBinding
及RendererBinding
,则必须GestureBinding
在RendererBinding
的前面,否则会报错。
当我们让GestureBinding
在RendererBinding
前面后,该错误就会消失。
当Dart
版本较低时,是没有mixin
及on
关键字的…
【参考资料】
《Dart编程语言》
理解Dart的Mixin继承机制
Dart for Flutter : Mixins in Dart
Dart: What are mixins?