Diamond继承和Common Lisp对象系统

时间:2014-11-22 04:46:58

标签: inheritance lisp common-lisp diamond-problem clos

我试图找到Common Lisp CLOS中典型钻石继承问题的解决方案。代码:

(defclass C1.0 () ... )
(defclass C2.1 (C1.0) ...)
(defclass C2.2 (C1.0) ...)
(defclass C3.0 (C2.1 C2.2) ...)

(defmethod m1 ((obj C1.0)) ...)
(defmethod m1 ((obj C2.1)) ...)
(defmethod m1 ((obj C2.2)) ...)
(defmethod m1 ((obj C3.0))
  ; Here I want to call the C2.2 version of
  ; m1
  ...)

还假设C1.0,C2.1和C2.2的代码在我无法访问的库中,所以我无法修改那里的任何内容。 Furthur,假设其他一些类也将派生自C2.2而 MAY NOT 想要调用m2的C2.2版本,所以我无法使用:before向C2.2添加任何内容。使用call-next-method将调用C2.1版本。

为了澄清,这是如何在Python中解决它:

class C1_0 :
  def m1 (self) : ...

class C2_1 (C1_0) :
  def m1 (self) : ...

class C2_2 (C1_0) :
  def m1 (self) : ...

class C3_0 (C2_1, C2_2) :
  def m1 (self) :
    C2_2.m1 (self)     # <-- This is the solution
    ...

3 个答案:

答案 0 :(得分:7)

如果要调用特定的类方法,则会破坏CLOS的目的。另请注意,CLOS比这更通用,因为它不仅支持多重继承,还支持多个分派。 Dispatch可以处理多个参数。因此,方法不属于类,方法的继承不是基于类继承,而是基于方法组合(通常使用类继承来以某种方式对方法进行排序)。

让我们看一下派发两个论点的方法:

(defmethod m1 ((obj1 C1.0) (obj2 C1.0)) ...)
(defmethod m1 ((obj1 C2.1) (obj2 C1.0)) ...)
(defmethod m1 ((obj1 C2.2) (obj2 C3.0)) ...)
(defmethod m1 ((obj1 C3.0) (obj2 C3.0)) ...)

请注意,当您调用泛型函数时,我们会讨论标准方法组合,其中通常会调用最具体的主要方法。在CLOS中,标准方法组合中的适用方法可以通过call-next-method调用 next 方法。这是直接调用继承功能的常用机制(还有:before:after:around方法可以提供继承功能。)

但另一个(简单)方法组合又如+progn还是and呢?在+的情况下,调用所有适用的方法并添加结果。

然后我们有类似的方法:

(defmethod m1 + ((obj1 C1.0) (obj2 C1.0))  1)
(defmethod m1 + ((obj1 C2.1) (obj2 C1.0))  2)
(defmethod m1 + ((obj1 C2.2) (obj2 C3.0)) 10)
(defmethod m1 + ((obj1 C3.0) (obj2 C3.0)) 20)

这样的简单方法组合通常不会在应用程序或库中使用。 CLOS还为用户指定的复杂方法组合提供了一种方法(例如:按合同设计功能可由CLOS中的用户实现)。

您可以在CLOS中解决它:您可以访问泛型函数的特定方法并调用其方法函数,但这很少使用,并且已经是元级功能。您还可以编写自己的方法组合,您可以在其中提供更复杂的调用方法。但这也很困难。

因此,考虑一个“钻石问题”可能是有意义的。只有当您考虑面向类的对象系统并尝试使用CLOS时才在CLOS中 - 这可以通过将CLOS使用限制为简单的情况来实现。但是CLOS没有解决方案来解决钻石问题。 CLOS通过将相同的插槽合并为一个来避免插槽。 CLOS通过提供一种将方法组织成泛型类并调用它们的不同方法来避免它的方法,首先通过方法组合组装它们。

答案 1 :(得分:0)

我在Clozure CL中找到了一种方法,但不确定它在主要的Common Lisp实现中的可移植性(或半便携式)。

以下代码将调用m1的C2.2版本:

(funcall
  (method-function (find-method #'m1 '() `(,(find-class 'C2.2))))
  obj)

答案 2 :(得分:0)

可以通过更改超类的顺序来更改发送顺序:

(defclass c3.2 (c2.2 c2.1)
  ...)

(defclass c3.1 (c2.1 c2.2)
  ...)

(m1 (make-instance 'c3.2)) ; calls the method specialized to c2.2

(m1 (make-instance 'c3.1)) ; calls the method specialized to c2.1