Skip to content

LISP - 类优先级

The Common Lisp 对象系统 (CLOS) 具有强大的多重继承系统。当一个类从多个父类(超类)继承时,可能会出现潜在的歧义:如果多个超类提供了同名的方法,应该使用哪一个?**类优先级(Class Precedence)**通过为每个类定义一个单一的、线性的继承顺序来解决这种歧义。

CLOS 使用复杂的算法(C3 方法解析顺序)为每个类计算一个类优先级列表 (CPL)。CPL 决定了完整的继承层次结构。规则可以简化如下:

  • 一个类总是比其超类具有更高的优先级。
  • 在 defclass 表单中列出的直接超类的顺序会被保留。
  • 最终列表是整个类层次结构的一致的、确定性的线性化。
  • 内置类 standard-object 和 t 始终位于列表末尾,提供一个共同的根。

您可以使用函数 (class-precedence-list (find-class 'class-name)) 检查任何类的 CPL。为了获得更易读的输出,我们可以将 class-name 映射到结果上。

让我们看看 defclass 中超类的顺序如何影响 CPL。

;;; main.lisp - 演示类优先级
(defclass a () ())
(defclass b () ())
;; 类 C 继承自 A,然后是 B
(defclass c (a b) ())
;; 类 D 继承自 B,然后是 A
(defclass d (b a) ())
(defun show-cpl (class-symbol)
"打印给定类符号的类优先级列表。"
(format t "CPL for ~a: ~a~%"
class-symbol
(mapcar #'class-name (ccl:class-precedence-list (find-class class-symbol)))))
(show-cpl 'a)
(show-cpl 'b)
(show-cpl 'c)
(show-cpl 'd)

注意: class-precedence-list 函数不是 ANSI 标准导出的。大多数实现都在 ccl、sb-mop 或 clos 等包中提供它。我们在这里使用 ccl:class-precedence-list 作为示例。

当您执行此代码时,输出清晰地显示了线性化顺序:

CPL for A: (A STANDARD-OBJECT T)
CPL for B: (B STANDARD-OBJECT T)
CPL for C: (C A B STANDARD-OBJECT T)
CPL for D: (D B A STANDARD-OBJECT T)
  • 对于 c,由于它定义为 (defclass c (a b) ()),其 CPL 为 (C A B ...)。类 A 优先于 B。
  • 对于 d,定义为 (defclass d (b a) ()),其 CPL 为 (D B A ...)。类 B 优先于 A。

CPL 的主要目的是在调用泛型函数(generic function)时确定执行哪个方法。CLOS 会找到给定参数的所有适用方法,并根据 CPL 确定并执行最特定的一个。

;; 定义一个泛型函数
(defgeneric identify (obj))
;; 定义类 A 和类 B 的方法
(defmethod identify ((obj a))
"为类 A 特化的方法")
(defmethod identify ((obj b))
"为类 B 特化的方法")
;; 创建 C 和 D 的实例
(let ((instance-c (make-instance 'c))
(instance-d (make-instance 'd)))
(format t "Calling identify on an instance of C: ~s~%" (identify instance-c))
(format t "Calling identify on an instance of D: ~s~%" (identify instance-d)))
Calling identify on an instance of C: "Method specialized for class A"
Calling identify on an instance of D: "Method specialized for class B"
  • 对于 instance-c,identify 的两个方法(针对 a 和 b)都适用。由于在 C 的 CPL 中 A 位于 B 之前,因此选择 A 的方法作为最特定的方法。
  • 对于 instance-d,情况则相反。在 D 的 CPL 中 B 位于 A 之前,因此选择 B 的方法。
  • 可预测性: 类优先级提供了一种可预测且强大的机制,用于解决复杂继承层次结构中的方法冲突。
  • 控制: 作为开发者,您可以通过在 defclass 中排列超类的顺序来控制优先级。
  • 调试: 当不确定方法行为时,检查 class-precedence-list 是调试的第一步。
  • 高级方法: 这是更高级功能的基础,例如方法组合(例如 :before、:after 和 :around 方法),它们允许多个方法对单个泛型函数调用做出贡献。