Mixin
Tip
当然多继承会有很多不足之处,例如,结构复杂化,有限顺序模糊,功能冲突等问题,举一个列子:
一个物体的本质只能有一个,一个动物只能是狗或者只能是猫,如果你想创造一个会玩毛线球会玩激光的狗,那么只需创造一个描述这类行为的接口,然后在自己的类里面实现”玩耍”接口,具体实现这些玩的行为,最终你同样会得到一个既像狗又像猫的动物。如果你想让这个动物叫起来像猫而不是狗,那么重写即可,子类里重新定义“叫”这个行为即可。但无论如何,这样得到的类是绝对不会有多重继承的冲突的。
对于Mixin并没有一个准确的概念,有人理解为Mix in混入。它类似于多继承,但通常混入Mixin的类和Mixin类本身斌那个不是is-a的关系。实质上Mixin是通过语言特性,来更简洁地实现组合模式。因此,Mixin可以灵活地添加某些功能。传统的接口概念中,并不包含实现部分,而Mixin包含实现。
基本概念
Mixins in Strongtalk建议了解一下Mixin学术文献资料,因为它定义了重要概念和注解。
注:Smalltalk被公认历史上第二个面向对象的程序设计语言和第一个真正的集成开发环境(IDE)。并且它对其它众多的程序设计语言的产生起到了极大的推动作用,主要有:Objective-c,Actor,Java和Ruby等,90年代的许多软件开发思想得利于Smalltalk,例如Design Patterns,Extreme Programming(XP)和Refactoring等。Strongtalk的最独特之处是支持渐进式的类型注解,这种思想在Dart,PHP,Python 3和TypeScript等语言中都有体现。Gilad Bracha是Dart开发团队的一员,在20世纪90年代,Gilad同Urs Hölzle和Lars Bak等人一起创建了语言Smalltalk的一个高性能版本即Strongtalk。但随着Java的流行,Sun停止了Strongtalk的投入,并将团队成员重新分配来优化Java的性能,而Strongtalk演变成了官方JVM即Hotspot. – 维基百科
作为一个支持类和继承的语言,类隐式地定义了Mixin。Mixin隐式地通过类主体(Class Body)进行定义,并建立子类和父类之间的变量增量(Delta)。而Class类实际上则是一个Mixin应用,即是通过隐式定义的Mixin应用于父类的结果。
Mixin Application
混合应用类似于Function Application函数应用。在数学中,混合类M可以视作从父类到子类新增的一个功能,将M注入超类S,并且返回一个S的子类。在研究文献中,这通常写作 M |> S
。
基于函数应用的概念,可以定义复合函数(即函数组合)。该概念贯穿于混合组合中。我们定义混合M1和M2的组合,写作M1*M2
,如(M1 * M2) |> S = M1 |> (M2 |> S)
。
函数非常有用,因为他们可以应用于不同的参数。同样,Class隐式定义的Mixin,通常仅在类声明给出的父类中应用一次。为允许Mixin应用于不同的父类,我们要么声明Mixin不依赖于特定的父类,要么脱离于Class隐式的Mixin,然后重用外部的原始定义。
语法和语义
Mixin通过正常的类声明被隐式定义。原则上,每个类都定义了一个Mixin,并可以从类中提取出来。然而,Mixin只能从未定义构造函数的类中提取。由于沿着继承链传递构造函数参数的需要,该约束能避免出现新的连锁问题。
举一个🌰:
|
Collection
上述中,事实上DOMElementList混合Collection mixin |> DOMList,而DOMElementSet则是Collection mixin |> DOMSet。
这样做的好处是,Collection中的代码可以在类的多个继承层次中被共享。因此,无论DOMList
还是DOMSet
,都不需要重复、复制Collection中的代码,并且任何变化都会使Collection传递到这两个继承结构中,大大简化了维护代码。上面的代码介绍了Mixin应用的一种方式:混合应用指定应用的Mixin和父类,以及混合应用的名称。
另外一种情况,混合应用出现在类声明的with
语句中,以逗号来分隔标识符列表的时候。此时,所有的标识符代表Class。在这种情况下,多混合在extends
语句中可以构成及应用于父类名称,生成一个匿名的父类。再以同样的🌰:
|
这里,DOMElementList
并不是应用Collection mixin |> DOMList。相反,它是一个父类为应用的新定义的类(注意extends关键字),DOMElementSet同样如此。注意,在每一种情况下,抽象函数newInstance()
必须单独实现,以便能够直接被实例化。
想象一下,如果DOMList
有一个带参数的构造函数:
|
构造函数可以为各个字段以及泛型参数设置值。每个Mixin都有一个自定义的构造函数被单独调用,父类也是如此。因为Mixin的构造函数不能够被声明,所以调用函数可以省略。在底层的实现中,调用总是放在初始列表之前。
第二种是以方便实用的语法糖形式,将多个Mixin混入类中,而不需要引入多个中间声明。举一个🌰:
|
这里,父类是一个混合应用:Demented mixin |> Aggressive mixin |> Musical mixin |> Person
假设Person
有带参数的构造函数,则Musical mixin |> Person将继承Person的构造函数。以此类推,一直到Maestro
实际的父类Person
,它由一系列的Mixin应用组成。
细节描述
Privacy私有
一个混合应用很可能是咋外部最初声明Class的库中被声明,这对访问混合应用的成员没有任何影响。根据混合应用的语义可知,访问成员取决于库最初声明的位置。这与普通的继承一样,是由底层语法(C++)的继承语义决定的。
Statics静态
是否可以通过混合应用使用最初Class的静态值?同样,由继承的语义进行分析,在Dart中静态成员不会被继承。
Types类型
混合应用的实例是什么类型?通常,它是父类的子类型,以及通过Mixin名称表示的子类型。换句话说,它与最初Class的类型相同。
最初的Class有它自身的父类。为确保特定的混合应用与最初进行混入的Class兼容,需要使用
with
语句。例如,如果通过with
语句定义了Class A,并应用了一个混合M,M源自Class K,那么A必须支持K定义的接口。