Dart:面向对象-多继承 with(Mixin)

  1. 背景
  2. 基本使用
  3. 函数重名冲突
  4. mixin 不能构造函数
  5. mixin on 限定条件
    1. 正确使用
    2. 两种错误示范
  6. 继承链

背景

  • Dart 不支持类的多继承(只能 extends 一个父类)。
  • 但是,有时候你希望一个类获得多个不同类的功能
  • 解决方案:Mixin(混入),用 with 关键字实现类似“多继承”的功能。

基本使用

// 在 Dart 之前的版本,mixin 是可以不加在类名前面的
mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

mixin class Android {
  void playStore() {
    print('Google play store');
  }
}

mixin class Ios {
  void appleStore() {
    print('Apply store');
  }
}

class Xiaomi with Phone, Android, Ios {}

void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 调用 Phone 的方法
  p.playStore(); // 调用 Android 的方法
  p.appleStore(); // 调用 Ios 的方法
}

函数重名冲突

在 Dart 里,with 多个 mixin 时,如果方法名字冲突,会按照 with 的从左到右的顺序 来“覆盖”前面的实现。

换句话说,后面的 mixin 优先级更高

mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

mixin class Android {
  void playStore() {
    print('Google play store');
  }

  void call() {
    print('Android phone is calling...');
  }
}

mixin class Ios {
  void appleStore() {
    print('Apply store');
  }

  void call() {
    print('Ios phone is calling...');
  }
}

class Xiaomi with Phone, Android, Ios {}

// 注意:如果有多个 mixin 中有同名方法,Dart 会按照从左到右的顺序调用,最后一个 mixin 的方法会覆盖前面的同名方法。
void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 最终调用的是最后一个 mixin 的 call 方法,即 Ios 的 call 方法
  p.playStore();
  p.appleStore();
}

mixin 不能构造函数

mixin class Android {
  String version = 'Android 12';
  // mixin 的构造函数不能有参数
  // The class 'Android' can't be used as a mixin because it declares a constructor
  // Android() {
  //   print('Android mixin constructor called');
  // }
  // Android(this.version);
  Android();// 这个构造函数是合法的,因为它没有参数 
  void playStore() {
    print('Google play store');
  }

  void call() {
    print('Android phone is calling...');
  }
}

为什么 mixin 不能有构造函数?

  • Mixin 本质上是用来给类“混入”额外功能的代码块,而不是完整的类。
  • 构造函数涉及对象的初始化和实例化,mixin 不负责实例化,因此不允许写构造函数。
  • 如果你需要构造函数,应该用普通类继承(extends)而不是 mixin。

mixin on 限定条件

什么是 mixin on 限定条件?

  • on 关键字用于限定某个 mixin 只能被指定类型(类)或其子类混入
  • 换句话说,on 限定了 mixin 的“作用范围”,只有继承(或实现)了指定基类的类,才能使用这个 mixin。

正确使用

mixin class Phone {
  void call() {
    print('Phone is calling...');
  }
}

// Android on Phone 表示 Android 是在 Phone 的基础上进行扩展
// 也可以理解为 Android 是 Phone 的一个子类
mixin Android on Phone {
  void playStore() {
    print('Google play store');
  }

  // 必须重新实现 call 方法么? // 不需要,因为 Android 已经继承了 Phone 的 call 方法
  // 如果不想覆盖 Phone 的 call 方法,可以不实现 call 方法
  // 如果想覆盖 Phone 的 call 方法,可以实现自己的 call 方法
  @override
  void call() {
    super.call(); // 调用 Phone 的 call 方法
    print('Android phone is calling...');
  }
}

// 继承了Phone,然后再继承 Android
// 这样 Xiaomi 就可以使用 Phone 和 Android 的方法
class Xiaomi with Phone, Android {} // 这是合法的,因为 Android 是 mixin,可以和 Phone 一起使用

// 继承了 Phone,然后 mixin 了 Android
class Xiaomi4 extends Phone with Android {}

// 实现了 Phone , 然后 mixin 了 Android
class Xiaomi5 implements Phone, Android {
  @override
  void call() {
    // TODO: implement call
  }

  @override
  void playStore() {
    // TODO: implement playStore
  }
}

void main(List<String> args) {
  var p = Xiaomi();
  p.call(); // 调用 Android 的 call 方法
  // Phone is calling...
  // Android phone is calling..
}

class Xiaomi with Phone, Android {}:正确,因为 Xiaomi 先混入了 Phone,再混入Android,所以符合 Android 的限制

class Xiaomi4 extends Phone with Android {}

  • 继承了 Phone,意味着 Xiaomi4Phone 的子类,自动继承了 Phone 的所有成员和实现。
  • 使用了 with Android 混入了 Android 的功能,Android 又限定了 on Phone,这里满足限制。
  • Android 可以调用 super.call(),调用的是 Phonecall()
  • 这是“继承 + mixin”的典型写法,既复用父类代码,也混入其他功能。

class Xiaomi5 implements Phone, Android { ... }

  • 实现了接口 PhoneAndroid,必须实现它们声明的所有方法。
  • implements 不继承实现,只是承诺“实现接口的方法”,所以 call()playStore() 都要自己写实现。
  • 适用于只关心接口规范,不想复用具体实现,或者需要自定义完全不同的实现。

两种错误示范

class Xiaomi2 with Android {}

  • Android 指明了 on Phone,它要求必须混入的类继承或实现 Phone
  • Xiaomi2 只混入了 Android,但它本身没有 extendswith Phone,这违反了 Android 的限制条件。

class Xiaomi3 with Android, Phone {}

  • Android 是一个限定为 on Phone 的 mixin,意思它只能被继承或混入了 Phone 的类使用。但你这里先写了 Android,而后写了 Phone
  • Dart 混入的顺序是从左到右:也就是说,Xiaomi3 先混入了 Android,但此时它还没有继承或混入 Phone,所以编译器会报错说 Android 的约束没满足。

继承链

在 Dart 里,类声明的语法是这样的:

class ClassName extends SuperClass with Mixin1, Mixin2, ... implements Interfaces {}

注意顺序:

  • extends 只能有一个,表示继承的父类;

  • with 后面可以有多个 mixin,按从左到右依次混入。

  • 方法解析顺序 (MROMRO, Method Resolution Order):

    当前类 → 第一个 mixin → 第二个 mixin → ... → 父类 → Object
    

如果我这么去写一个类:

class Sub with MixinA extends Dad {}

展开后的继承链是:

Sub → MixinA → Dad → Object

以下是一个完整的例子:

class Dad {
  void say() => print("Dad");
}

mixin MixinA {
  void say() => print("MixinA");
}

class Sub with MixinA extends Dad {
  @override
  void say() {
    print("Sub");
    super.say();
  }
}

void main() {
  Sub().say();
}
/*日志:
      Sub
      MixinA
*/

Sub 中写的 super.say(),查找顺序是:

  1. 往继承链上一级(即 MixinA)找 say 方法;
  2. 如果 MixinA 没有实现,就继续找 Dad
  3. 如果 Dad 也没有,就继续找 Object
  4. 找不到就报错。

×

喜欢就点赞,疼爱就打赏