11.7 辅助方法 (Auxiliary Methods)

方法可以通过如 :before:after 以及 :around 等辅助方法来增强。 :before 方法允许我们说:“嘿首先,先做这个。” 最具体的 :before 方法优先被调用,作为其它方法调用的序幕 (prelude)。 :after 方法允许我们说 “P.S. 也做这个。” 最具体的 :after 方法最后被调用,作为其它方法调用的闭幕 (epilogue)。在这之间,我们运行的是在这之前仅视为方法的方法,而准确地说应该叫做主方法 (primary method)。这个主方法调用所返回的值为方法的返回值,甚至 :after 方法在之后被调用也不例外。

:before:after 方法允许我们将新的行为包在调用主方法的周围。 :around 方法提供了一个更戏剧的方式来办到这件事。如果 :around 方法存在的话,会调用的是 :around 方法而不是主方法。则根据它自己的判断, :around 方法自己可能会调用主方法(通过函数 call-next-method ,这也是这个函数存在的目的)。

这称为标准方法组合机制 (standard method combination)。在标准方法组合机制里,调用一个通用函数会调用

  1. 最具体的 :around 方法,如果有的话。

  2. 否则,依序,

    1. 所有的 :before 方法,从最具体到最不具体。
    2. 最具体的主方法
    3. 所有的 :after 方法,从最不具体到最具体

返回值为 :around 方法的返回值(情况 1)或是最具体的主方法的返回值(情况 2)。

辅助方法通过在 defmethod 调用中,在方法名后加上一个修饰关键字 (qualifying keyword)来定义。如果我们替 speaker 类别定义一个主要的 speak 方法如下:

  1. (defclass speaker () ())
  2. (defmethod speak ((s speaker) string)
  3. (format t "~A" string))

则使用 speaker 实例来调用 speak 仅印出第二个参数:

  1. > (speak (make-instance 'speaker)
  2. "I'm hungry")
  3. I'm hungry
  4. NIL

通过定义一个 intellectual 子类,将主要的 speak 方法用 :before:after 方法包起来,

  1. (defclass intellectual (speaker) ())
  2. (defmethod speak :before ((i intellectual) string)
  3. (princ "Perhaps "))
  4. (defmethod speak :after ((i intellectual) string)
  5. (princ " in some sense"))

我们可以创建一个说话前后带有惯用语的演讲者:

  1. > (speak (make-instance 'intellectual)
  2. "I am hungry")
  3. Perhaps I am hungry in some sense
  4. NIL

如同先前标准方法组合机制所述,所有的 :before:after 方法都被调用了。所以如果我们替 speaker 基类定义 :before:after 方法,

  1. (defmethod speak :before ((s speaker) string)
  2. (princ "I think "))

无论是哪个 :before:after 方法被调用,整个通用函数所返回的值,是最具体主方法的返回值 ── 在这个情况下,为 format 函数所返回的 nil

而在有 :around 方法时,情况就不一样了。如果有一个替传入通用函数特别定义的 :around 方法,则优先调用 :around 方法,而其它的方法要看 :around 方法让不让它们被运行。一个 :around 或主方法,可以通过调用 call-next-method 来调用下一个方法。在调用下一个方法前,它使用 next-method-p 来检查是否有下个方法可调用。

有了 :around 方法,我们可以定义另一个,更谨慎的, speaker 的子类别:

  1. (defclass courtier (speaker) ())
  2. (defmethod speak :around ((c courtier) string)
  3. (format t "Does the King believe that ~A?" string)
  4. (if (eql (read) 'yes)
  5. (if (next-method-p) (call-next-method))
  6. (format t "Indeed, it is a preposterous idea. ~%"))
  7. 'bow)

当传给 speak 的第一个参数是 courtier 类的实例时,朝臣 (courtier)的舌头有了 :around 方法保护,就不会被割掉了:

  1. > (speak (make-instance 'courtier) "kings will last")
  2. Does the King believe that kings will last? yes
  3. I think kings will last
  4. BOW
  5. > (speak (make-instance 'courtier) "kings will last")
  6. Does the King believe that kings will last? no
  7. Indeed, it is a preposterous idea.
  8. BOW

记得由 :around 方法所返回的值即通用函数的返回值,这与 :before:after 方法的返回值不一样。