2

我有以下两个课程:

(defclass person () ())

(defmethod speak ((s person) string)
    (format t "-A" string))

(defmethod speak :before ((s person) string)
    (print "Hello! "))

(defmethod speak :after ((s person) string)
    (print "Have a nice day!"))


(defclass speaker (person) ())

(defmethod speak ((i speaker) string)
  (print "Bonjour!"))


(speak (make-instance 'speaker) "Can I help yoU?")

这个的输出是:

"Hello! "                                                                                                                                                                                                                                               
"Bonjour!"                                                                                                                                                                                                                                              
"Have a nice day!" 

我想弄清楚的是这些方法是如何按照“顺序”执行的。我似乎无法理解正在发生的事情以及原因。据说有一个规则优先级,但我不知道在哪里可以找到它。例如,为什么"Hello!Can I help you"在这种情况下不开火?

4

3 回答 3

9

当没有 around 方法时,方法应用的顺序是:所有 before 方法从最具体到最不具体,然后是最具体的主要方法,然后是 after 方法,从最不具体到最具体。在您的情况下,您有两种主要方法(名称旁边没有 :before 或 :after 的方法),一种指定人,另一种指定扬声器。由于说话人比人更具体,因此只调用说话人主要方法。如果要调用多个主要方法,请查看call-next-method

于 2015-06-05T03:14:47.330 回答
3

虽然我看到已经有一个公认的答案,但 Common Lisp 在 HyperSpec 中有一些非常好的文档,知道在哪里可以找到所发生事件的完整描述很有用。在这种情况下,它是7.6.6.2 Standard Method Combination,它说(缩写):

标准方法组合的语义如下:

  • 如果有任何环绕方法,则调用最具体的环绕方法。它提供通用函数的一个或多个值。

  • 在 around 方法体内,可以使用 call-next-method 调用下一个方法。当 next 方法返回时,around 方法可以执行更多代码,可能基于返回的一个或多个值。如果使用 call-next-method 并且没有可调用的适用方法,则调用通用函数 no-next-method。函数 next-method-p 可用于确定是否存在下一个方法。

  • 如果周围方法调用 call-next-method,则调用下一个最具体的周围方法(如果适用)。如果没有周围方法,或者如果 call-next-method 被最不具体的周围方法调用,则调用其他方法如下:

    • 所有之前的方法都以最具体的优先顺序被调用。它们的值被忽略。如果在 before 方法中使用 call-next-method,则会发出错误信号。

    • 调用最具体的主要方法。在主要方法的主体中, call-next-method 可用于调用下一个最具体的主要方法。当该方法返回时,前一个主要方法可以执行更多代码,可能基于返回的值或值。如果使用 call-next-method 并且没有更多适用的主要方法,则调用通用函数 no-next-method。函数 next-method-p 可用于确定是否存在下一个方法。如果不使用 call-next-method,则只调用最具体的主要方法。

    • 所有 after 方法都以最具体的最后顺序调用。它们的值被忽略。如果在 after 方法中使用 call-next-method,则会发出错误信号。

  • 如果没有调用周围方法,则最具体的主要方法提供泛型函数返回的一个或多个值。在最不具体的环绕方法中调用 call-next-method 返回的值是最具体的主要方法返回的值。

该页面末尾有一个特别有用的插图,描述了行为及其动机:

before 方法以最具体的优先顺序运行,而 after 方法以最不具体​​的优先顺序运行。这种差异的设计原理可以用一个例子来说明。假设类 C1 通过添加 before 方法和 after 方法来修改其超类 C2 的行为。类 C2 的行为是直接由 C2 上的方法定义还是从其超类继承,不影响在类 C1 的实例上调用方法的相对顺序。C1 类的 before 方法在 C2 类的所有方法之前运行。C1 类的 after 方法在 C2 类的所有方法之后运行。

相比之下,all around 方法在任何其他方法运行之前运行。因此,一个不太具体的环绕方法在一个更具体的主要方法之前运行。

如果只使用primary方法并且不使用call-next-method,则只调用最具体的方法;也就是说,更具体的方法会影响更一般的方法。

于 2015-06-05T11:38:18.873 回答
1

除了其他答案,请注意您可以使用以下宏定义自定义方法组合: DEFINE-METHOD-COMBINATION. 已经有十个现有的方法组合器,所以我认为定义自定义组合器并不常见。当然,能够这样做有时会非常有用(参见 Joshua Taylor 的评论)。

此外,调用方法的方式受类继承的影响,默认情况下会考虑父子关系以及超类之间的顺序。请阅读“CLOS 基础”。可以使用元对象协议更改类优先级列表:请参阅COMPUTE-CLASS-PRECEDENCE-LIST

于 2015-06-07T11:42:27.307 回答