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的结论与第二条一样,混入并不会覆盖被混入类自带的同名方法
所以结论是
- 后面混入的覆盖前面混入的Mixin的同名方法
- Mixin不会覆盖被混入类自带的同名方法
- 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 {
}