现在 combine
有一个方法。若我们在此时调用 combine
,我们会获得由传入的两个参数所组成的一个列表:
> (combine 'a 'b)
(A B)
到现在我们还没有做任何一般函数做不到的事情。一个通用函数不寻常的地方是,我们可以继续替它加入新的方法。
首先,我们定义一些可以让新的方法引用的类别:
(defclass stuff () ((name :accessor name :initarg :name)))
(defclass ice-cream (stuff) ())
(defclass topping (stuff) ())
这里定义了三个类别: stuff
,只是一个有名字的东西,而 ice-cream
与 topping
是 stuff
的子类。
现在下面是替 combine
定义的第二个方法:
(defmethod combine ((ic ice-cream) (top topping))
(format nil "~A ice-cream with ~A topping."
(name ic)
在这次 defmethod
的调用中,参数被特化了 (specialized):每个出现在列表里的参数都有一个类别的名字。一个方法的特化指出它是应用至何种类别的参数。我们刚定义的方法仅能在传给 combine
的参数分别是 ice-cream
与 的实例时。
但使用其他参数时,我们会得到我们第一次定义的方法:
> (combine 23 'skiddoo)
(23 SKIDDOO)
因为第一个方法的两个参数皆没有特化,它永远只有最低优先权,并永远是最后一个调用的方法。一个未特化的方法是一个安全手段,就像 case
表达式中的 otherwise
子句。
一个方法中,任何参数的组合都可以特化。在这个方法里,只有第一个参数被特化了:
(defmethod combine ((ic ice-cream) x)
(format nil "~A ice-cream with ~A."
(name ic)
x))
若我们用一个 ice-cream
的实例以及一个 topping
的实例来调用 combine
,我们仍然得到特化两个参数的方法,因为它是最具体的那个:
> (combine (make-instance 'ice-cream :name 'grape)
(make-instance 'topping :name 'marshmallow))
然而若第一个参数是 ice-cream
而第二个参数不是 topping
的实例的话,我们会得到刚刚上面所定义的那个方法:
当一个通用函数被调用时,参数决定了一个或多个可用的方法 (applicable methods)。如果在调用中的参数在参数的特化约定内,我们说一个方法是可用的。
在前面的例子里,很容易看出哪个是最具体的可用方法,因为所有的对象都是单继承的。一个 ice-cream
的实例是,按顺序来, , stuff
, standard-object
, 以及 t
类别的成员。
方法不需要在由 defclass
定义的类别层级来做特化。他们也可以替类型做特化(更精准的说,可以反映出类型的类别)。以下是一个给 combine
用的方法,对数字做了特化:
(defmethod combine ((x number) (y number))
(+ x y))
方法甚至可以对单一的对象做特化,用 eql
来决定:
(defmethod combine ((x (eql 'powder)) (y (eql 'spark)))
'boom)
单一对象特化的优先级比类别特化来得高。
方法可以像一般 Common Lisp 函数一样有复杂的参数列表,但所有组成通用函数方法的参数列表必须是一致的 (congruent)。参数的数量必须一致,同样数量的选择性参数(如果有的话),要嘛一起使用 &rest
或是 &key
参数,或者一起不要用。下面的参数列表对是全部一致的,
(x) (a)
(x &optional y) (a &optional b)
(x y &rest z) (a b &key c)
(x y &key z) (a b &key c d)
而下列的参数列表对不是一致的:
'kaboom)
我们重定义了当 combine
方法的参数是 powder
与 时, combine
方法干了什么事儿。