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)。在标准方法组合机制里,调用一个通用函数会调用
最具体的
:around
方法,如果有的话。否则,依序,
- 所有的
:before
方法,从最具体到最不具体。 - 最具体的主方法
- 所有的
:after
方法,从最不具体到最具体
- 所有的
返回值为 :around
方法的返回值(情况 1)或是最具体的主方法的返回值(情况 2)。
辅助方法通过在 defmethod
调用中,在方法名后加上一个修饰关键字 (qualifying keyword)来定义。如果我们替 speaker
类别定义一个主要的 speak
方法如下:
(defclass speaker () ())
(defmethod speak ((s speaker) string)
(format t "~A" string))
则使用 speaker
实例来调用 speak
仅印出第二个参数:
> (speak (make-instance 'speaker)
"I'm hungry")
I'm hungry
NIL
通过定义一个 intellectual
子类,将主要的 speak
方法用 :before
与 :after
方法包起来,
(defclass intellectual (speaker) ())
(defmethod speak :before ((i intellectual) string)
(princ "Perhaps "))
(defmethod speak :after ((i intellectual) string)
(princ " in some sense"))
我们可以创建一个说话前后带有惯用语的演讲者:
> (speak (make-instance 'intellectual)
"I am hungry")
Perhaps I am hungry in some sense
NIL
如同先前标准方法组合机制所述,所有的 :before
及 :after
方法都被调用了。所以如果我们替 speaker
基类定义 :before
或 :after
方法,
(defmethod speak :before ((s speaker) string)
(princ "I think "))
无论是哪个 :before
或 :after
方法被调用,整个通用函数所返回的值,是最具体主方法的返回值 ── 在这个情况下,为 format
函数所返回的 nil
。
而在有 :around
方法时,情况就不一样了。如果有一个替传入通用函数特别定义的 :around
方法,则优先调用 :around
方法,而其它的方法要看 :around
方法让不让它们被运行。一个 :around
或主方法,可以通过调用 call-next-method
来调用下一个方法。在调用下一个方法前,它使用 next-method-p
来检查是否有下个方法可调用。
有了 :around
方法,我们可以定义另一个,更谨慎的, speaker
的子类别:
(defclass courtier (speaker) ())
(defmethod speak :around ((c courtier) string)
(format t "Does the King believe that ~A?" string)
(if (eql (read) 'yes)
(if (next-method-p) (call-next-method))
(format t "Indeed, it is a preposterous idea. ~%"))
'bow)
当传给 speak
的第一个参数是 courtier
类的实例时,朝臣 (courtier)的舌头有了 :around
方法保护,就不会被割掉了:
> (speak (make-instance 'courtier) "kings will last")
Does the King believe that kings will last? yes
I think kings will last
BOW
> (speak (make-instance 'courtier) "kings will last")
Does the King believe that kings will last? no
Indeed, it is a preposterous idea.
BOW
记得由 :around
方法所返回的值即通用函数的返回值,这与 :before
与 :after
方法的返回值不一样。