Dart之Mixin详解

6,478 阅读7分钟

随着Dart学习的深入,发现了一个比较棘手的语法——mixin。它对于Java开发人员来说,是一个全新的概念,但也是深入学习Dart无法绕过的一个槛。

那么mixin到底是什么尼???下面来看维基百科对它的定义。

mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法、变量而不必成为其子类。

简而言之,mixins是普通的类,我们可以从中扩展方法(或变量)而不扩展类。

应用场景

首先我们来看一个应用场景。

图片来自于Dart: What are mixins?

在上图中,有一个类——Animal,它有三个子类——MammalBirdFish,而这三个类也有其对应的子类。

下面分别通过JavaDart来实现上面这些类及其继承关系。

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 {...}

代码几乎一摸一样,下面就根据上图来分别给这些类添加行为——WalkSwimFlying。由于这些行为并不是所有类通用的,所以不能将这些行为放在父类。但如果把这三个行为分别放在三个类中,然后让其他类来继承这三个类,也就可以解决上述问题。但这样就是多继承,而JavaDart又不支持多继承。所以这时候凸显出接口的重要性,通过接口来实现上述行为。代码如下:

//行走行为
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替换成了withabstract class替换成了mixin嘛,也太简单了。但仔细一看,我们发现mixin里有方法的具体实现,这样可以避免接口的方法必须在子类实现从而导致的代码冗余(Java 8通过关键字default也可以做到这一点)问题。简而言之,mixin相对于接口能够更好的避免代码冗余,使代码更加集中。

其实笔者之所以学习mixin,是因为在flutter源码中大量使用了mixin,而如果不了解mixin的话,就无法分析flutter源码。哈哈哈......

mixin之线性化

在上面的示例中,我们发现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<String> arguments) {
  var binding = WidgetsFlutterBinding();
  binding.initInstances();
}

WidgetsFlutterBinding中并没有重写initInstances方法,那么就以最右边重写该方法的类——WidgetsBinding为主。那么结果应该如下。

但其实真实结果其实如下。

这是为什么尼?仔细一点就可以发现,我们在WidgetsBindingRendererBindingGestureBinding中都调用了父类的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过程中遇到的几个问题。

注意一

mixin类要么是直接继承Object,要么是直接或间接继承extends关键字后的类。在前面的示例上做一些修改,如下。

//一个新类
abstract class Binding {
  void initInstances() {
    print("Binding——initInstances");
  }
}
//让GestureBinding类继承自Binding
mixin GestureBinding on Binding {
    @Override
    void initInstances() {
//        super.initInstances();
    }
}

这时候我们就会发现代码报错,出现了如下警告。

当我们再次让GestureBinding继承自BindingBase时,上面错误就消失了。所以with后面的mixin类必须要与当前类继承自同一父类,如上面的WidgetsFlutterBindingGestureBinding必须都要继承自BindingBase

注意二

如果类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后加上类GestureBindingRendererBinding,则必须GestureBindingRendererBinding的前面,否则会报错。

当我们让GestureBindingRendererBinding前面后,该错误就会消失。

Dart版本较低时,是没有mixinon关键字的......

【参考资料】

《Dart编程语言》

理解Dart的Mixin继承机制

Dart for Flutter : Mixins in Dart

Dart: What are mixins?