17.2 多重继承 (Multiple Inheritance)

到目前为止我们只有单继承 ── 一个对象只可以有一个父类。但可以通过使 parent 属性变成一个列表来获得多重继承,并重新定义 rget ,如图 17.2 所示。

在只有单继承的情况下,当我们想要从对象取出某些属性,只需要递归地延着祖先的方向往上找。如果对象本身没有我们想要属性的有关信息,可以检视其父类,以此类推。有了多重继承后,我们仍想要执行同样的搜索,但这件简单的事,却被对象的祖先可形成一个图,而不再是简单的树给复杂化了。不能只使用深度优先来搜索这个图。有多个父类时,可以有如图 17.3 所示的层级存在: a 起源于 bc ,而他们都是 d 的子孙。一个深度优先(或说高度优先)的遍历结果会是 a , b , d, c , d 。而如果我们想要的属性在 dc 都有的话,我们会获得存在 d 的值,而不是存在 c 的值。这违反了子类可覆写父类提供缺省值的原则。

如果我们想要实现普遍的继承概念,就不应该在检查其子孙前,先检查该对象。在这个情况下,适当的搜索顺序会是 a , b , c , d 。那如何保证搜索总是先搜子孙呢?最简单的方法是用一个对象,以及按正确优先顺序排序的,由祖先所构成的列表。通过调用 traverse 开始,建构一个列表,表示深度优先遍历所遇到的对象。如果任一个对象有共享的父类,则列表中会有重复元素。如果仅保存最后出现的复本,会获得一般由 CLOS 定义的优先级列表。(删除所有除了最后一个之外的复本,根据 183 页所描述的算法,规则三。)Common Lisp 函数 delete-duplicates 定义成如此作用的,所以我们只要在深度优先的基础上调用它,我们就会得到正确的优先级列表。一旦优先级列表创建完成, rget 根据需要的属性搜索第一个符合的对象。

我们可以通过利用优先级列表的优点,举例来说,一个爱国的无赖先是一个无赖,然后才是爱国者:

  1. > (setf scoundrel (make-hash-table)
  2. patriot (make-hash-table)
  3. patriotic-scoundrel (make-hash-table)
  4. (gethash 'serves scoundrel) 'self
  5. (gethash 'serves patriot) 'country
  6. (gethash :parents patriotic-scoundrel)
  7. (list scoundrel patriot))
  8. (#<Hash-Table C41C7E> #<Hash-Table C41F0E>)
  9. > (rget 'serves patriotic-scoundrel)
  10. SELF
  11. T

到目前为止,我们有一个强大的程序,但极其丑陋且低效。在一个 Lisp 程序生命周期的第二阶段,我们将这个初步框架提炼成有用的东西。