一、概念
“Mixins are a way of reusing a class’s code in multiple class hierarchies.” 混合是一种在多个类层次结构中重用类代码的方法。
从概念上我们可以理解它是为了解决代码重用的一种方式。学习java的小伙伴可能会想到interface,Dart中的类都可以作为接口,这不是已经有了
解决方案了吗?为什么还需要mixin呢?
二、使用
首先我们来解决上面提到的问题,为什么要使用mixin。
我们很多语言都是采用了单继承+接口多实现的方式,但是这种方式不能很好的适用于所有场景。
我们看下下面这个假设的例子:
我们通过上图可以看出,这些学生都有一个共同的父类Student,然后又有三个抽象子类:文科班学生,理科班学生、艺术班学生。这些类具有相同的行为和能力,但是有的类又有自己独有的行为和能力。比如文科、理科、艺术班学生都可以上数学课程,
但是只有理科班学生可以上物理课程,文科班和艺术班学生可以上地理课程,物理班学生又不可以上地理课程。那么问题就出现了,部分具有相同能力和行为的子类都要保留一份相同的代码实现,比如文科班学生类和艺术班学生类都要实现一份上地理课的实现,
就产生了冗余。这种单继承模型下,无法把部分子类具有相同行为能力抽象到基类中,因为对其它不具有此行为的子类来说是不合适的,例如把上地理课放在基类student中,因此只能在各自子类中实现。
abstract class Student{
}
abstract class GeographyClass{
void goGeography();
}
abstract class MathClass{
void goMath();
}
abstract class PhysicsClass{
void goPhysics();
}
class ScienceStudent extends Student implements MathClass, GeographyClass {
@override
void goGeography() {
print("上地理课");
}
@override
void goMath() {
print("上数学课");
}
}
class LiberalStudent extends Student implements MathClass, PhysicsClass {
@override
void goMath() {
print("上数学课");
}
@override
void goPhysics() {
print("上物理课");
}
}
class ArtStudent extends Student implements MathClass, GeographyClass {
@override
void goGeography() {
print("上地理课");
}
@override
void goMath() {
print("上数学课");
}
}
从上述实现代码中可以看出,子类中很多相同的冗余实现代码。那么我们是不是想有一种方式可以解决这种冗余问题,是的,mixin就能很好的解决这类问题。
下面是使用mixin改写后的代码:
abstract class Student{}
mixin GeographyClass{
void goGeography() {
print("上地理课");
}
}
mixin MathClass{
void goMath() {
print("上数学课");
}
}
mixin PhysicsClass{
void goPhysics() {
print("上物理课");
}
}
class ScienceStudent extends Student with MathClass, GeographyClass {
}
class LiberalStudent extends Student with MathClass, PhysicsClass {
}
class ArtStudent extends Student with MathClass, GeographyClass {
}
改写后的代码可以发现,mixin很好得解决了代码冗余问题。它能复用类中的某个功能具体实现,而不是像接口一样需要类去实现哪些能力。因此mixin多继承模型很好解决了单继承模型带来的冗余问题。
注意:对mixin关键字的支持是在Dart 2.1 版本引入的,早期的版本使用abstract class
来代替的。
其实在java8和Kotlin中为了解决代码重复冗余问题,使用了接口的default实现来解决这个问题:
interface ITest {
default void test(){}
}
三、线性化
mixin是线性化的,这句话如何理解呢?首先我们看下下面的例子:
mixin TA {
void t() {
print("TA");
}
}
mixin TB {
void t() {
print("TB");
}
}
class TC {
void t() {
print("TC");
}
}
class Mix1 with TA, TB {//TB
}
class Mix2 with TB, TA {//TA
}
我们会得到如下结果:
TB
TA
此时你可能会总结得到规律,with后面多个类中有相同的方法,会调用距离with关键字最远的类中的方法。下面我们得到结论:
mixin混入类中时,Dart中的Mixins通过创建一个新类来实现,该类将mixin的实现层叠在一个超类之上以创建一个新类 ,它不是“在超类中”,而是在超类的“顶部。
声明 mixin 的顺序代表了继承链的继承顺序,声明在后面的 mixin,一般会最先执行。
接下来我们再看下面的例子:
class T {
void fun() {
print("A");
}
}
mixin TA on T{
void fun() {
super.fun();
cover();
print("TA");
}
void cover() {
print("cover TA");
}
}
mixin TB on T {
void fun() {
super.fun();
print("TB");
}
void cover() {
print("cover TB");
}
}
class A extends T with TA, TB {} // TB
class B extends T with TB, TA {} // TA
void main() {
A a = A();
a.fun();
}
按照我们刚才的理解,我们会得到如下结果:
A
cover TA
TA
TB
实际上,我们输出的结果是:
A
cover TB
TA
TB
回想我们得到的结论,在mixin继承链中,最后声明的mixin会把前面声明的相同方法覆盖掉。这时,即使我们代码中调用了TA的cover方法,实际上也会被TB类中的cover方法覆盖掉。因此最终调用的还是MB中的方法。
四、总结
我们大致总结了mixin的机制和使用,mixin 是一个强大的概念,我们可以跨越类的层次结构重用代码。在我们看Flutter源码时,经常会看到使用这个功能,我也是在看Flutter代码时,看到这个关键字然后进行补脑的。
1.Mixins并不是经典意义上获得多重继承的方法。
2.Mixins是一种抽象和复用一系列操作和状态的方式,而且生成多个中间的mixin类。
3.它是线性的,因此与单继承兼容。
4.Mixins除了继承Object外,不可以继承任何其他类; Mixins不可以定义构造方法。