js-mixin: https://www.yuque.com/zhuchaoyang/wrif6k/iinaz1#0mtvR
https://resocoder.com/2019/07/21/mixins-in-dart-understand-dart-flutter-fundamentals-tutorial/

没有mixin的世界

以例说明:

假设你在构建一个模拟野生动物的app,那么你需要一个Mosquito(蚊子)类。你会抽象蚊子们有的共通的东西然后放在一个抽象类里。

  1. abstract class Insect {
  2. void crawl() {
  3. print('爬行');
  4. }
  5. }
  6. abstract class AirborneInsect extends Insect {
  7. void fly() {
  8. print('飞行');
  9. }
  10. void buzz() {
  11. print('嗡嗡响');
  12. }
  13. }
  14. class Mosquito extends AirborneInsect {
  15. void doMosquitoThing() {
  16. crawl();
  17. fly();
  18. buzz();
  19. print('吸血');
  20. }
  21. }

添加新的昆虫,根本不会有代码的冗余出现。
一直到你发现你需要一个Swallow类(就是一种可以吃掉整个蚊子的东西)。

同样的也有很多鸟类共有的东西,我们可以创建一个Bird类。这个时候问题就出现了,鸟也会振动翅膀!但是,你没法把fly方法从AirboneInsect类里面提取出来组成一个新的类Flying

为什么?Bird类可以继承Flying类,但是AirboneInsect不可以,它已经继承了Insect类了。Dart可不支持多继承

这下,你需要给Bird类添加一个fly方法了。代码冗余发生了!

  1. abstract class Bird {
  2. void chirp() {
  3. print('鸣叫');
  4. }
  5. // 冗余的方法
  6. void fly() {
  7. print('飞行');
  8. }
  9. }
  10. class Swallow extends Bird {
  11. void doSwallowThing() {
  12. chirp();
  13. fly();
  14. print('吃蚊子');
  15. }
  16. }

使用mixins

在经典的面向对象编程语言里一定会有常规的类抽象类接口

mixins 的中文意思是混入,就是在类中混入其他功能。Mixins的定义是“一种把类代码用在多个继承树的方法”。即:它是一种在多个类层次结构中复用类代码的方法,简单的说,就是可以允许你通过非继承的方式来复用类中的代码。

通常,只要一个类是继承自Object的而且没有定义构造方法,那么这个类可以是一个Mixin了。当然,如果你想让mixin的定义更加的清晰,可以使用mixin关键字开头来定义。具体请参考这里

因为mixins使用的条件,随着Dart版本一直在变,这里讲的是Dart2.1中使用mixins的条件:

  1. mixins类只能继承自object
  2. mixins类不能有构造函数
  3. 一个类可以mixins多个mixins类
  4. 可以mixins多个类,不破坏Flutter的单继承

mixins可以用在常规的类上面也可以用在抽象类,只需要一个with关键字。

怎么来理解with呢?
继承 -> extends
mixins -> with
**
继承和mixins是一样的,是语言的特性,withextends 是关键字。

最简单的mixin

mixin本身可以是抽象的,可以定义各种方法属性,也可以是抽象的,等后续类去实现

  1. mixin A {
  2. int age = 1;
  3. run() {
  4. print('A -- run');
  5. }
  6. init();
  7. }
  8. class Person with A {
  9. // A实例的init方法没有方法体,Person必须要实现,否则报错
  10. @override
  11. init() {
  12. print('Person -- $age');
  13. print('Person -- init');
  14. }
  15. }
  16. void main() {
  17. var p = Person();
  18. p.run(); //A -- run
  19. print(p.age); //1
  20. p.init();
  21. // Person -- 1
  22. // Person -- init
  23. }

同名覆盖顺序问题

  1. class A {
  2. log() => print('A');
  3. }
  4. mixin B {
  5. log() => print('B');
  6. }
  7. class Base {
  8. log() => print('Base');
  9. }
  10. class Demo1 extends Base {}
  11. class Demo2 extends Base with A, B {}
  12. class Demo3 extends Base with B, A {}
  13. class Demo4 extends Base with A, B {
  14. @override
  15. log() => print('Demo');
  16. }
  17. // Demo4 -> B -> A -> Base
  18. void main() {
  19. Demo1().log(); //Base
  20. Demo2().log(); //B
  21. Demo3().log(); //A
  22. Demo4().log(); //Demo
  23. }

基于某个类型的mixin

当mixin使用 on 关键字基于某类型时,则该mixin只能在这个类的子类中使用。
mixin中自然也可以调用这个类定义的方法、属性。

  1. class Base {
  2. log() {
  3. print('Base -- log');
  4. }
  5. }
  6. mixin A on Base {
  7. int age = 1;
  8. run() {
  9. print('A -- run');
  10. }
  11. init() {
  12. log();
  13. }
  14. }
  15. // A 必须是 Base 的子类,才可以混入Test
  16. // class Person with A{} //报错
  17. class Person extends Base with A {}
  18. void main() {
  19. var p = Person();
  20. p.run(); //A -- run
  21. print(p.age); //1
  22. p.init();
  23. // Base -- log
  24. }

多个mixin

如果mixin存在冲突的部分,后面会覆盖前面的,没有冲突的则会保留,所以可以存在后面的mixin修改了前面的mixin的一部分逻辑的情况,不需要直接继承即可实现覆盖,避免了更复杂的继承关系。

  1. mixin A {
  2. int age = 1;
  3. run() {
  4. print('A -- run');
  5. }
  6. init();
  7. }
  8. mixin B {
  9. int age = 2;
  10. close() {
  11. print('B -- close');
  12. }
  13. }
  14. class Person with A, B {
  15. @override
  16. init() {
  17. print('Person -- init');
  18. }
  19. }
  20. void main() {
  21. var p = Person();
  22. p.run(); //A -- run
  23. print(p.age); //2
  24. p.init(); //Person -- init
  25. p.close(); //B -- close
  26. }

多重继承

  1. import 'dart:developer';
  2. class Base {
  3. init() {
  4. print('Base init');
  5. }
  6. Base() {
  7. print('Base 构造函数开始');
  8. init();
  9. print('Base 构造函数结束');
  10. }
  11. }
  12. mixin A on Base {
  13. init() {
  14. print('A init start');
  15. super.init();
  16. print('A init end');
  17. }
  18. }
  19. mixin B on Base {
  20. init() {
  21. print('B init start');
  22. super.init();
  23. print('B init end');
  24. }
  25. }
  26. class Person extends Base with A, B {
  27. @override
  28. init() {
  29. print('Person init start');
  30. super.init();
  31. print('Person init end');
  32. }
  33. }
  34. void main() {
  35. var p = Person();
  36. // Base 构造函数开始
  37. // Person init start
  38. // B init start
  39. // A init start
  40. // Base init
  41. // A init end
  42. // B init end
  43. // Person init end
  44. // Base 构造函数结束
  45. }

前文模拟野生动物的app示例

mixin可以用在常规的类上面也可以用在抽象类,只需要一个with关键字。在野生动物模拟app例子里,你也许要用在抽象类上了。

  1. //飞行特性
  2. mixin Flying {
  3. void fly() {
  4. print('flying');
  5. }
  6. }
  7. //昆虫类
  8. abstract class Insect {
  9. void crawl() {
  10. print('crawling');
  11. }
  12. }
  13. //飞行昆虫类
  14. abstract class AirborneInsect extends Insect with Flying {
  15. void buzz() {
  16. print('buzzing annoyingly');
  17. }
  18. }
  19. //鸟类
  20. abstract class Bird with Flying {
  21. void chirp() {
  22. print('chirping');
  23. }
  24. }
  1. //蚊子
  2. class Mosquito extends AirborneInsect {
  3. void doMosquitoThing() {
  4. crawl();
  5. fly();
  6. buzz();
  7. print('sucking blood');
  8. }
  9. }
  10. //吃蚊子的某种生物
  11. class Swallow extends Bird {
  12. void doSwallowThing() {
  13. chirp();
  14. fly();
  15. print('eating a mosquito');
  16. }
  17. }
  18. void main() {
  19. Mosquito().doMosquitoThing(); //crawling -> flying -> buzzing annoyingly -> sucking blood
  20. Swallow().doSwallowThing(); //chirping -> flying -> eating a mosquito
  21. }

也可以在一个类上面使用多个mixin。如果需要在Bird类上用一个chirping mixn的话,就和Flying mixin一起用就好。

  1. abstract class Bird with Flying, Chirping

结论

Mixin对于继承体系中避免代码的冗余非常有用处。mixin也可以被约束在只可以用在某些特定的类上面,这让他们成为了开发的一大利器!