Dart Mixins混入机制详解

前言

对于Java或C#开发人员来说,第一次接触到mixin可能会有点懵,因为这是一种新的语言特性,比如笔者在Flutter动画开发中用到的AnimationLocalStatusListenersMixin 就是一个Mixin

创建Mixins

mixin AnimationLocalStatusListenersMixin {
  final ObserverList<AnimationStatusListener> _statusListeners = ObserverList<AnimationStatusListener>();

  void addStatusListener(AnimationStatusListener listener) {
    didRegisterListener();
    _statusListeners.add(listener);
  }

  void removeStatusListener(AnimationStatusListener listener) {
    final bool removed = _statusListeners.remove(listener);
    if (removed) {
      didUnregisterListener();
    }
  }
  
  // 省略其余代码
}

 用with关键字使用Mixins

class AnimationController extends Animation<double>
  with AnimationEagerListenerMixin, AnimationLocalListenersMixin, AnimationLocalStatusListenersMixin {
   // 省略具体代码
}

是什么

mixin即mix in,中文翻译过来是“混入”,就是在类中混入其他功能

在Dart官网中的定义是Mixins are a way of reusing code in multiple class hierarchies.翻译过来就是“Mixins是一种在多类层次结构中复用代码的一种方式”

在维基百科中Mixin的定义如下:

Mixin是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类。Mixin有时被称作"included"而不是"inherited"。mixin为使用它的class提供额外的功能,但自身却不单独使用(不能单独生成实例对象,属于抽象类)。因为有以上限制,Mixin类通常作为功能模块使用,在需要该功能时“混入”,而且不会使类的关系变得复杂。使用者与Mixin不是“is-a”的关系,而是「-able」关系
Mixin有利于代码复用又避免了多继承的复杂。[3][4]使用Mixin享有单一继承的单纯性和多重继承的共有性。接口与mixin相同的地方是都可以多继承,不同的地方在于 mixin 是带实现的。Mixin也可以看作是带实现的interface。这种设计模式实现了依赖反转原则。

为什么

Mixin最核心的功能就是代码复用,但是代码复用有多种方法,例如Java中有如下几种代码复用方法,为何要用Mixin呢?

  • 继承:继承可以复用父类的代码,但是java里只支持单继承,而mixin可以混入多个类
  • 接口:接口可以实现多继承,但是接口里的方法未实现,不同子类有不同实现,严格来说并未实现代码的复用,而Mixin除了支持多继承外,可以包含实现的方法
  • 组合:在类里包含某个类,使用其方法达到代码复用。但是这种方法比较繁琐,每个类里还需额外多一个或多个成员变量。Mixin就比较简洁,只需在类定义时用with关键字混入Mixin类即可

怎么用

首先用mixin关键字定义一个mixin类

对于mixin关键字可以这样理解,定义类用class,定义接口用interface,而定义Mixins用的就是mixin

mixin A {
  void a() {
    print('A');
  }
}

然后用with关键字混入一个mixin类

对于with关键字也可以这样理解,集成类用extends,实现接口用implements,而混入Minxins用的就是with

class Mix with A {
  
}

OK,现在在类B中就可以使用A中的功能了,如下代码输出‘A’

void main() {
  Mix m = Mix();
  m.a();
}

多混入

上面说过,Mixins支持多混入,这样就可以使用多个Mixin类的功能

如下的Mix类混入了A、B两个类

mixin A {
  void a() {
    print('A');
  }
}

mixin B {
  void b() {
    print('B');
  }
}

class Mix with A, B {
  
}

void main() {
  Mix m = Mix();
  m.a();
  m.b();
}

输出结果为

A
B

限制条件

1.Mixins除了继承Object外,不可以继承任何其他类

例如如下代码Mixin A继承类Clz,就会报错

class Clz {}
mixin A extends Clz {
  void a() {
    print('A');
  }
}

报如下错误

Expected 'on' instead of this.
'A' can't be mixed onto 'Object' because 'Object' doesn't implement 'Clz'.

2.Mixins不可以定义构造方法

例如如下Mixin定义中有构造方法,会报Mixins can't declare constructors.错误

mixin A {
  A() {
    
  }
  void a() {
    print('A');
  }
}

方法覆盖

既然Mixins支持多混入,那么

1.如果混入的多个Mixin中定义了相同的方法,那么调用谁的

2.如果Minxin中定义了与被混入类中相同的方法,那么调用谁的

3.如果Minxin中定义了与被混入类的父类相同的方法,那么调用谁的

带着这几个疑问,我们先写个demo跑一下,看下结果,然后再做总结

mixin A {
  void p() {
    print('A');
  }
}
mixin B {
  void p() {
    print('B');
  }
}
class C {
  void p() {
    print('C');
  }
}

class Mix1 with A, B {}
class Mix2 with B, A {}
class Mix3 with A, B {
  void p() {
    print('Mix3');
  }
}

class Mix4 extends C with A, B {}
class Mix5 extends C with B, A {}
class Mix6 extends C with A, B {
  void p() {
    print('Mix3');
  }
}


void main() {
  Mix1 m1 = Mix1();
  Mix2 m2 = Mix2();
  Mix3 m3 = Mix3();
  Mix4 m4 = Mix4();
  Mix5 m5 = Mix5();
  Mix6 m6 = Mix6();
  
  m1.p();
  m2.p();
  m3.p();
  m4.p();
  m5.p();
  m6.p();
}

输出结果如下

B
A
Mix3
B
A
Mix3

分析:

1.对比Mix1和Mix2的输出结果可知,后面混入的覆盖前面混入的类的同名方法

2.由Mix3的结果可知 ,混入并不会覆盖被混入类自带的同名方法

3.由Mix4和Mix5的的结果可知,混入会覆盖被混入类的父类同名方法

4.Mix6的结论与第二条一样,混入并不会覆盖被混入类自带的同名方法

所以结论是

  1. 后面混入的覆盖前面混入的Mixin的同名方法
  2. Mixin不会覆盖被混入类自带的同名方法
  3. Mixin会覆盖被混入类的父类同名方法

mixin on

还记得上面限制条件中提到的Minxins不能继承其他类,否则会报Expected 'on' instead of this.错误

class Clz {}
mixin A extends Clz {
  void a() {
    print('A');
  }
}

若按照错误提示将extends改成on,错误确实消失了

class Clz {}
mixin A on Clz {
  void a() {
    print('A');
  }
}

但是此时on并不表示继承,而是表示一种限制条件,表示被混入的类必须是on后面限定的类的子类

如下所示Mix并不是Clz的子类,会报'A' can't be mixed onto 'Object' because 'Object' doesn't implement 'Clz'.错误

class Clz {}
mixin A on Clz {
  void a() {
    print('A');
  }
}

class Mix with A {
  
}

让Mix继承Clz即可

class Clz {}
mixin A on Clz {
  void a() {
    print('A');
  }
}

class Mix extends Clz with A {
  
}

 

posted @ 2019-11-07 16:55  野猿新一  阅读(78)  评论(0编辑  收藏  举报