11.5 优先级 (Precedence)

我们已经看过类别是怎样能有多个基类了。当一个实例的方法同时属于这个实例所属的几个类时,Lisp 需要某种方式来决定要使用哪个方法。优先级的重点在于确保这一切是以一种直观的方式发生的。

每一个类别,都有一个优先级列表:一个将自身及自身的基类从最具体到最不具体所排序的列表。在目前看过的例子中,优先级还不是需要讨论的议题,但在更大的程序里,它会是一个需要考虑的议题。

以下是一个更复杂的类别层级:

  1. (defclass sculpture () (height width depth))
  2. (defclass statue (sclpture) (subject))
  3. (defclass metalwork () (metal-type))
  4. (defclass casting (metalwork) ())
  5. (defclass cast-statue (statue casting) ())

图 11.3 包含了一个表示 cast-statue 类别及其基类的网络。

../_images/Figure-11.3.png

图 11.3: 类别层级

要替一个类别建构一个这样的网络,从最底层用一个节点表示该类别开始。接着替类别最近的基类画上节点,其顺序根据 defclass 调用里的顺序由左至右画,再来给每个节点重复这个过程,直到你抵达一个类别,这个类别最近的基类是 standard-object ── 即传给 defclass 的第二个参数为 () 的类别。最后从这些类别往上建立链接,到表示 standard-object 节点为止,接着往上加一个表示类别 t 的节点与一个链接。结果会是一个网络,最顶与最下层各为一个点,如图 11.3 所示。

一个类别的优先级列表可以通过如下步骤,遍历对应的网络计算出来:

  1. 从网络的底部开始。
  2. 往上走,遇到未探索的分支永远选最左边。
  3. 如果你将进入一个节点,你发现此节点右边也有一条路同样进入该节点时,则从该节点退后,重走刚刚的老路,直到回到一个节点,这个节点上有尚未探索的路径。接着返回步骤 2。
  4. 当你抵达表示 t 的节点时,遍历就结束了。你第一次进入每个节点的顺序就决定了节点在优先级列表的顺序。

这个定义的结果之一(实际上讲的是规则 3)在优先级列表里,类别不会在其子类别出现前出现。

图 11.3 的箭头演示了一个网络是如何遍历的。由这个图所决定出的优先级列表为: cast-statue , statue , sculpture , casting , metalwork , standard-object , t 。有时候会用 specific 这个词,作为在一个给定的优先级列表中来引用类别的位置的速记法。优先级列表从最高优先级排序至最低优先级。

优先级的主要目的是,当一个通用函数 (generic function)被调用时,决定要用哪个方法。这个过程在下一节讲述。另一个优先级重要的地方是,当一个槽从多个基类继承时。408 页的备注解释了当这情况发生时的应用规则。 λ