Beyond Single Inheritance.

继承是 OOP 的灵魂之一:But so far all our examples have been classes with 1 (immediate) superclass. If inheritance is so powerful, why not allow ways to use more code defined in other places?

下面我们将介绍三个有紧密联系,但不同的 ideas;它们均能在一定程度上实现比单继承更加强大的代码复用。由强到弱分别是:multiple inheritance (多继承)mixins (混入)interfaces (接口)

C++: Multiple Inheritance

Languages with multiple inheritance let one class extend multiple other classes.

当某个类同时具有多个父类的特征时,多继承是一个很自然的想法。举例来说,考虑类 Person 的两个子类 CowboyArtist,当我们想实现一个艺术家牛仔时,我们仅需要定义同时继承自 Cowboy 类与 Artist 类的 CowboyArtist 类即可。

Intuitively,我们将多继承定义为子类继承多个父类中的所有方法与成员变量,但我们能够很容易发现这个定义的不妥之处:若多个父类中的方法或成员变量存在重复,或者冲突,又如何处理?

一个典型的例子是同名方法问题。若 Cowboy 类中定义了方法 draw (draw a gun, 指拔枪),且 Artist 类中也定义了方法 draw (draw a picture, 指画画),那么 CowboyArtist 类该如何继承这两个行为完全不同的同名方法?若两个方法都继承,又应该如何进行区分?

在支持多继承的语言中,多继承带来的语义问题需要通过定义相当复杂的 rules for how subclassing, method lookup, and field access work 来解决。 举例来说,C++ 有至少两种创建子类的方式。

  • 子类继承所有来自父类的方法与成员变量。
  • 对于继承自共同祖先的成员变量,仅仅拷贝一份。

在单继承中,所有的类与继承关系形成的 hierarchy 是一颗树,而多继承形成的则是一个 DAG。在设计多继承的相关规则时,继承自共同祖先 (common ancester) 的方法与成员变量常常需要被特殊考虑,因为它们很可能会导致重复或冲突 (若在两个子类中分别被重写)。

Ruby: Mixins

Ruby has mixins, which are somewhere between multiple inheritance (above) and interfaces (below).

They provide actual code to classes that include them, but they are not classes themselves, so we cannot create instances of them.

我们使用 module 关键字来定义 mixins,在类的定义中使用 include 关键字来“继承” mixins。

1
2
3
4
5
module Doubler
def double
self + self # use self's + message, not defined in Doubler
end
end

一般来说,我们尽量避免在 mixins 中定义实例变量。mixins 比较优雅的用法应是:They define methods that call other methods on self that are not defined by mixins.

如上例,mixin Doubler 可以被任意实现了 + 方法的类 include:可以是某个自定义的类 (AnotherPt),也可以是标准库中的一些符合要求的类 (如 String)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AnotherPt
attr_accessor :x, :y
include Doubler
def + other # add two points
ans = AnotherPt.new
ans.x = self.x + other.x
ans.y = self.y + other.y
ans
end
end

class String
include Doubler
end

现在类 AnotherPt 与类 String 的实例都能够直接调用 Doubler 方法了。

在引入 mixins 后,之前我们介绍的 method lookup rules 也应作出一些改变;在沿着 class hierarchy 向上寻找 method 的过程中也要考虑类中 include 的 mixin。If obj is an instance of class C and we send message m to obj:

  • First look in the class C for a definition of m.
  • Next look in mixins included in C, Later includes shadow earlier ones.
  • Recur with the superclass of C and its included mixins to see if they defines m.

Java/C#: Interfaces

在 Java 或 C# 中,一个类只能有一个直接父类,但却可以实现 (implement) 任意数量的 interfaces。

一个 interface 声明了若干方法,且规定了这些方法的参数与返回值的类型 (type)。注意,interface 中声明的方法是待实现的,其函数体部分都是空置的。

正是因为 interface 并不真正“定义”方法,而仅仅对方法命名并规定其类型,在 multiple inheritance 中出现的语义问题将得以避免。If two interfaces have a method-name conflict, a class can still implement them both. If two interfaces disagree on a method’s type, no class can implement them both since type-checker will catch that.

A class type-checks only if it actually provides (directly or via inheritance) all the methods of all the interfaces it claims to implement.

关于 interface 还有一个很重要的概念:An interface is a type。所以如果类 C 实现了 interface I,我们就能将类 C 的实例传入某个形参类型为 I 的方法,这也是另一种意义上的 duck typing。

与 multiple inheritance 和 mixins 不同,interfaces 并不继承任何代码;它的存在使得某些 statically typed languages 的 type system 更加灵活,允许了某种程度上的 duck typing。

对于不存在 type system 限制的 dynamically typed languages,we can already pass any object to any method and call any method on any object (这也是 dynamic typing 的 essence 所在),自然 interfaces 的存在就失去了意义。

笔记联动:Algorithm, Princeton - Java Language,这里介绍了 Iterator, Comparator 等等 Java 中常用的 interfaces。