我有以下两个类:
(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"
不开火?
当您没有任何around方法时,方法应用的顺序是:在方法之前,从最具体到最不具体,然后是最具体的primary方法,然后是在方法之后,从最不具体到最具体。在你的例子中,你有两个主要方法(名字旁边没有:before或:after的方法),一个指定person,另一个指定speaker。因为speaker比person更具体,所以只调用speaker primary方法。如果您想调用多个主方法,请查看call-next-method。
虽然我看到已经有一个公认的答案,但是Common Lisp在HyperSpec中有一些非常好的文档,知道在哪里可以找到发生的事情的完整描述是很有用的。在本例中,它是7.6.6.2标准方法组合,它表示(缩写):
标准方法组合的语义如下:
如果存在任何around方法,则调用最具体的around方法。它提供泛型函数的一个或多个值。
在一个around方法的内部,call-next-method可以用来调用next方法。当next方法返回时,around方法可以根据返回值执行更多代码。如果call-next-method为,则调用泛型函数no-next-method已使用且没有可调用的方法。这个函数next-method-p可用于确定是否存在下一个方法。
如果一个around方法调用了call-next-method,那么下一个最具体的around方法将被调用(如果有的话)。如果身边没有人方法,或者如果call-next-method被最不具体的周围调用方法,其他方法按如下方式调用:
所有的before方法被调用,以最具体的第一顺序。它们的值被忽略了。表示错误,如果call-next-method用于before方法。
调用最具体的primary方法。在主方法的主体中,可以使用call-next-method调用下一个方法具体的主要方法。当该方法返回时,前一个方法可以执行更多的代码,也许是基于返回的值或值。调用泛型函数no-next-method使用了Call-next-method,并且没有更多适用的primary方法。函数next-method-p可用于确定aNext方法存在。如果不使用call-next-method,则只使用most
所有的after方法都是按照最具体的最后顺序调用的。它们的值被忽略了。如果call-next-method是,则会发出错误信号
如果没有调用周围方法,最具体的主方法提供泛型函数返回的值或值。的对象中调用call-next-method返回的值最不具体的方法是由最具体的方法返回的主要方法。
在那一页的末尾有一个特别有用的插图,描述了这种行为及其动机:
before方法以最具体的第一顺序运行,而在方法以最不具体的优先顺序运行之后。设计这种差异的基本原理可以用一个例子来说明。假设类C1修改了它的超类C2的行为,通过添加方法前和方法后。的行为是否类C2是由C2上的方法直接定义的,或者是从它的类调用的相对顺序不受超类的影响类C1的实例上的方法。类C1的before方法运行在类C2的所有方法之前。类C1的after方法在之后运行所有类C2的方法
相比之下,所有around方法在任何其他方法运行之前运行。因此不太具体的around方法在更具体的primary方法之前运行方法。如果只使用主方法并且没有使用call-next-method,只调用最特定的方法;也就是说,更具体
除了其他答案,请注意您可以使用以下宏定义自定义方法组合:DEFINE-METHOD-COMBINATION
。已经有十个现有的方法组合子,所以我不认为定义自定义组合子是常见的。当然,能够这样做有时是非常有用的(参见Joshua Taylor的评论)。
同样,调用方法的方式受类继承的影响,默认情况下会考虑父子关系,以及超类之间的顺序。请阅读"CLOS基本原理"。类的优先级列表可以通过元对象协议来改变:参见COMPUTE-CLASS-PRECEDENCE-LIST
。