文章目录
  1. 1. 摘要
  2. 2. 介绍

本文翻译自《Traits:Composable Units of Behaviour》by Nathanael Schärli, Stéphane Ducasse, Oscar Nierstrasz, and Andrew P. Black

这是我在学习Scala trait时看到的一篇paper,从理论上讲解了trait和mixin,也分析了多继承的一些问题,很有学习的价值

摘要

尽管在面向对象编程语言的世界里,继承在构件重用机制里起了中流砥柱的作用,但它的一些变种,例如单继承、多继承、混入继承(mixin),却一直有着理论和实现上的种种问题。在这篇paper的第一部分,我们举例来说明这些问题。接着我们提出trait,一种简单的用于构建面向对象语言的组合模型(compositional model)。一个trait,其实就是一组方法的集合,它们是构建一个类的基本单元,也是代码重用的基本单元。在trait模型中,类由一组traits组成,以及包含一些胶水代码用于组合这些traits并记录状态。我们展示trait如何克服多继承、混入继承带来的问题,以及如何高效地实现trait,同时也总结了如何用trait来重构一个类层次(class hierarchy)。

介绍

尽管单继承被广泛认为是面向对象的必要元素,程序员却渐渐意识到单继承在很多时候不能够有效地从一个复杂的类结构中提取出共用特性(变量和方法)。所以,语言的设计者们提出了多种多继承的机制,以及混入继承的机制,使得一个类能够从一系列的特征构建起来。

但将近二十年过去了,并没有什么多继承或混入继承获得广泛认可。在总结Alan Snyder于OOPSLA 87的会议上对于继承模式的贡献时,Steve Cook写到:

“多继承确实有价值,但没有好的实现方式。”

所以,总的趋势离多继承越来越远:最近的一些编程语言例如Java和C#的设计者认为,多继承带来的复杂性远比它的实际价值要大。而多继承确实也存在一些实现上的问题,我们也确信,它同样存在理论上的问题。在对这个问题进行研究之后,我们提出了trait的设计。

尽管多继承使得能够重用任何类,但类并不是合适的重用单元。这是因为类其实扮演了两个相互矛盾的角色,类既扮演了对象产生者的角色(类可以实例化成一个对象),因此它必须是完整的;但作为一个重用单元,一个类应当尽可能小。这些属性常常会引起冲突。此外,作为一个对象生产者,每个类需要在类层次结构有一个独一无二的位置,然而作为重用单元,类应该能够在任意位置。

Moon’Flavors(应该是一个冰淇淋)在早期提出这个问题:口味通常是一种很小的属性,并且也不需要是完整的,它可以混入到整个层次的任意位置。其他一些复杂的混入随后由Bracha、Cook、Mens、van Limberghen、Flatt、Krishnamurthi、Felleisen、Ancona、Lagorio、Zucca提出。这些方式允许程序员创建重用组件,但这些组件不需要被实例化。但是,据我们了解,这种方式会影响程序的可读性。

混入使用了单继承的符号,来把它扩展到多个基类。但是,尽管这个继承操作符适合从已有的类继承,它却不适合把多个重用组件组合到一起。特别是,使用继承来实现混入,使得混入必须被线性地组合起来,这严重地限制了使用胶水代码的能力,因为我们需要这些胶水代码来把混入合并到一起。

因此,我们提出了一种叫做traits的轻量级代码重用单元的概念。traits的设计从重用性和可读性的矛盾开始。总的来说,我们相信如果一个程序能够用多种形式来解读,它的可读性会更高。尽管一个类能够通过组合小的traits来构建,但我们其实可以用多种眼光来看待这个类。它可以被看作是一个扁平化的方法集合,或者是一个由traits组成的合成物。扁平化的视图有助于理解;而合成试图则有助于重用。到目前位置还没有什么冲突,这两种概念能够和平共处,不过有一个限制条件,这种组合必须只能作为一种架构工具,不会改变类的涵义。

traits刚好满足了这个条件。它为类提供了结构化、模块化、重用性,并且如果用类的眼光来看待,这些traits又可以被忽略掉。trait在重用性和可读性之间取得了一个完美平衡,同时也实现了更好的概念建模。并且,由于traits仅仅关心行为的重用性,并不关心状态的重用,这就避免了在多继承和混入中遇到的实现问题。

traits具有以下特性:

  • 一个trait提供了一系列的方法,并且包含实现(译者注:Java8之前的的interface不能包含实现)
  • 一个trait需要一些方法作为参数,来获得提供的其他方法
  • traits不声明任何状态,其提供的方法也不会直接访问状态变量
  • 类和traits能够由其他的traits合成,但合成顺序是不相干的。有冲突的方法必须要显式决议。
  • trait组合不影响一个类的涵义:这些从trait获得的方法,即便是直接定义在class中,也不会改变类的涵义
  • 类似的,一个trait组合也不会影响trait的语义:一个组合trait,等价于把这些trait的方法直接写在这个组合trait里面

一个类能够继承一个基类,然后添加一系列的traits,以及必要的状态变量和方法。这些添加的方式其实就是指定了traits如何连接到一起,如何解决它们之间的冲突。这种方式使得一个类能够由一系列的特性——traits,然后用一些胶水代码把这些特性组合到一起。由于一个方法的语义跟它定义在trait里或是定义在使用这个trait的类里无关,所以把一个组合的trait在任意层次扁平化都是没有问题的。

本文的贡献主要包括提出多继承和混入的问题,以及提出了traits这一组合模型来解决这些问题。我们遵循以下顺序:第二节描述了多继承和混入的问题,第三节提出traits以及一些样例。在第四节讨论了最重要的设计决策,并以此来解决第二节提出的问题。第五节提出了traits的实现。第六节总结了traits的现实应用:对Smalltalk-80集合框架的重构。第七节讨论相关工作。第八节对本文做了总结,并指出未来的工作。

文章目录
  1. 1. 摘要
  2. 2. 介绍