Skip to content

LISP - `:before` 方法组合

在 Common Lisp 对象系统(CLOS)中,方法组合(method combination)控制着单个泛型函数(generic function)的不同方法(method)如何组合与执行。标准方法组合提供了几种辅助方法类型,包括 :before 方法。:before 方法是一个强大工具,用于在主方法(primary method)被调用之前执行代码。

:before 方法的常见用例包括:

  • 输入验证:在主逻辑运行前检查参数。
  • 设置:确保前置条件或状态已正确建立。
  • 日志记录和追踪:为调试打印消息或追踪方法调用。
  • 预备计算:执行计算,并将结果存储起来供后续使用(例如,存储在 slot 中)。

你使用 defmethod 定义 :before 方法,其中 :before 限定符位于泛型函数名称和参数列表之间。至关重要的是,:before 方法的返回值总是被忽略的。

;; 1. Define a generic function.
(defgeneric process-data (data))
;; 2. Define a :before method that applies to ALL types of data (specialized on T).
(defmethod process-data :before ((data t))
(format t "[LOG] Preparing to process data: ~a~%" data))
;; 3. Define a more specific :before method just for integers.
(defmethod process-data :before ((data integer))
(format t "[VALIDATE] Integer-specific check for ~a passed.~%" data))

当一个泛型函数被调用时,CLOS 首先会以从最具体到最通用的顺序执行所有适用的 :before 方法。所有 :before 方法运行完毕后,单个最具体的主方法(primary method)才会被执行。

;;; --- Generic Function Definition ---
(defgeneric process-data (data))
;;; --- :before Methods (for setup and logging) ---
;; Most-general :before method
(defmethod process-data :before ((data t))
(format t "Preparing to process data: ~a~%" data))
;; More-specific :before method for integers
(defmethod process-data :before ((data integer))
(format t "Integer-specific preparation for: ~a~%" data))
;;; --- Primary Methods (for the main logic) ---
;; Primary method for integers
(defmethod process-data ((data integer))
(format t "--> Processing integer: ~a, result: ~a~%" data (* data 2)))
;; Primary method for strings
(defmethod process-data ((data string))
(format t "--> Processing string: ~a, result: ~a~%" data (concatenate 'string data "-processed")))
;;; --- Calling the Generic Function ---
(print "--- Calling with an integer ---")
(process-data 10)
(print "--- Calling with a string ---")
(process-data "test")

当你执行这段代码时,方法会按照特定顺序组合并调用:

"--- Calling with an integer ---"
Integer-specific preparation for: 10
Preparing to process data: 10
--> Processing integer: 10, result: 20
"--- Calling with a string ---"
Preparing to process data: "test"
--> Processing string: "test", result: "test-processed"
  • 当 (process-data 10) 被调用时:
    1. 最具体的 :before 方法 ((data integer)) 首先运行。
    1. 接着运行不太具体的 :before 方法 ((data t))。
    1. 最后,最具体的主方法 ((data integer)) 运行。
  • 当 (process-data “test”) 被调用时:
    1. 只有一个 :before 方法适用 ((data t)),因此它运行。
    1. 最具体的主方法 ((data string)) 运行。
  • 常见错误:尝试从 :before 方法返回一个值以停止执行或改变主方法的参数。这是行不通的。它们的返回值会被丢弃。要达到这种程度的控制,你必须使用 :around 方法。
  • 最佳实践:保持 :before 方法简单,并专注于单一任务,例如验证或日志记录。它们不应包含复杂的应用逻辑。它们旨在为主方法做准备,而不是执行主方法的工作。