Skip to content

LISP - 方法组合

Lisp - 高级面向对象编程:方法组合(Method Combination)

Section titled “Lisp - 高级面向对象编程:方法组合(Method Combination)”

Common Lisp 对象系统(CLOS)是现有的最强大的对象系统之一。其一个关键的独特特性是方法组合(method combination),它定义了 CLOS 如何为单个泛型函数(generic function)调用组合多个适用方法的行为。这使得程序设计极其灵活和模块化。

首先,快速回顾一下:

  • **泛型函数(generic function)**定义了一个操作的接口(例如,draw-shape)。
  • **方法(method)**为特定参数类型提供了该泛型函数的具体实现(例如,一个针对 (draw-shape ((s circle))) 的方法,以及另一个针对 (draw-shape ((s square))) 的方法)。

方法组合回答了这样一个问题:当继承导致多个方法可能适用于单个调用时,会发生什么?

这是默认且最常见的方法组合类型。它协调四种不同类型的方法,它们以特定的分层顺序运行。我们可以使用准备一顿饭的类比来理解它:

  • 1. :around 方法:主厨。拥有最终控制权。它可以运行自己的代码,通过使用 (call-next-method) 决定是否继续执行其余的方法,甚至可以改变最终结果。最具体的 :around 方法首先运行。
  • 2. :before 方法:准备工作。这些方法都在主操作之前运行,按从最具体到最不具体的顺序执行(例如,先洗蔬菜,再切蔬菜)。它们不提供返回值。
  • 3. Primary 方法:主菜。只有单个最具体的 primary 方法运行。它定义了核心行为并为此次“餐点”提供返回值(除非 :around 方法将其覆盖)。
  • 4. :after 方法:收尾工作。这些方法都在主操作之后运行,按从最具体到最不具体的相反顺序执行(例如,先清理台面,再洗碗)。它们不提供返回值。

让我们定义一个类层次结构和一个包含所有四种方法类型的泛型函数。

;; main.lisp
(defclass vehicle () ())
(defclass car (vehicle) ())
;; 1. 定义泛型函数
(defgeneric perform-maintenance (v))
;; 2. 定义方法
;; :around 方法 - 拥有全局控制权
(defmethod perform-maintenance :around ((v vehicle))
(format t "[AROUND] Starting maintenance check for ~a.~%" (class-name (class-of v)))
(let ((report (call-next-method)))
(format t "[AROUND] Maintenance complete. Report: ~a~%" report)
(cons 'completed report)))
;; :before 方法 - 按最具体优先顺序运行
(defmethod perform-maintenance :before ((v vehicle))
(format t " [BEFORE] Checking tire pressure.~%"))
(defmethod perform-maintenance :before ((v car))
(format t " [BEFORE] Checking engine oil level.~%"))
;; Primary 方法 - 只有最具体的那个运行
(defmethod perform-maintenance ((v vehicle))
(format t " [PRIMARY] Washing the vehicle.~%")
'(washed))
(defmethod perform-maintenance ((v car))
(format t " [PRIMARY] Rotating tires.~%")
'(tires-rotated))
;; :after 方法 - 按最具体最后顺序运行
(defmethod perform-maintenance :after ((v vehicle))
(format t " [AFTER] Logging maintenance in vehicle logbook.~%"))
(defmethod perform-maintenance :after ((v car))
(format t " [AFTER] Resetting 'Service Needed' light.~%"))
;; 3. 调用泛型函数
(perform-maintenance (make-instance 'car))
; 对 'car' 实例的执行流程:
; 1. :around 方法启动。
; 2. 它调用 call-next-method,触发 :before 方法(先 car,后 vehicle)。
; 3. 最具体的 primary 方法(car)运行并返回 '(tires-rotated)'。
; 4. :after 方法运行(先 vehicle,后 car)。
; 5. 控制权返回到 :around 方法,它包装了结果。
[AROUND] Starting maintenance check for CAR.
[BEFORE] Checking engine oil level.
[BEFORE] Checking tire pressure.
[PRIMARY] Rotating tires.
[AFTER] Logging maintenance in vehicle logbook.
[AFTER] Resetting 'Service Needed' light.
[AROUND] Maintenance complete. Report: (TIRES-ROTATED)
; 返回值 -> (COMPLETED TIRES-ROTATED)

CLOS 提供了其他组合方法的方式,其中所有适用的 primary 方法都对结果有所贡献。你在 defgeneric 形式中指定这一点。

  • +:所有适用方法的结果被加在一起。
  • append:所有方法的列表结果被追加到一个单一列表中。
  • and:如果任何方法返回 nil,组合将停止并返回 nil。否则,返回最后一个方法的值。
  • list:将所有方法的结果收集到一个列表中。
(defclass item () ())
(defclass taxable-item (item) ())
(defclass shipping-item (item) ())
(defgeneric get-total-cost (i)
(:method-combination + :most-specific-first))
(defmethod get-total-cost + ((i item)) 100) ; Base price
(defmethod get-total-cost + ((i taxable-item)) 8) ; Tax
(defmethod get-total-cost + ((i shipping-item)) 15) ; Shipping
;; 一个既可征税又可配送的物品
(defclass book (taxable-item shipping-item) ())
(get-total-cost (make-instance 'book))
;=> 123 (100 + 8 + 15)
  • 关注点分离(Separation of Concerns):将核心逻辑保留在 primary 方法中,而将日志记录、安全性或缓存等外围任务放在 :before、:after 或 :around 方法中处理。
  • 框架可扩展性(Framework Extensibility):允许库的用户无需修改原始源代码即可介入和自定义行为。
  • 模块化设计(Modular Design):通过小型、可重用且独立的方法构建复杂行为。