Lisp 如何将超类对象传递给子类构造函数?

Lisp 如何将超类对象传递给子类构造函数?,lisp,common-lisp,clos,Lisp,Common Lisp,Clos,假设我有一个带有两个插槽的类A: (defclass a () ((a-1 :initarg :a-1) (a-2 :initarg :a-2))) 以及继承自A的类B: (defclass b (a) ((b-1 :initarg :b-1))) 如果我想实例化B,生成实例将为我提供插槽:a-1,:a-2和:B-1 这里有一个疯狂的想法:如果我想使用a的现有实例实例化B,并且只填充槽B-1,该怎么办 注:为什么有用:如果A实现了一些B直接继承的通用方法,而不添加任何新的内容。

假设我有一个带有两个插槽的类
A

(defclass a ()
  ((a-1 :initarg :a-1)
   (a-2 :initarg :a-2)))
以及继承自
A
的类
B

(defclass b (a)
  ((b-1 :initarg :b-1)))
如果我想实例化
B
生成实例
将为我提供插槽
:a-1
:a-2
:B-1

这里有一个疯狂的想法:如果我想使用
a
的现有实例实例化
B
,并且只填充槽
B-1
,该怎么办

注:为什么有用:如果
A
实现了一些
B
直接继承的通用方法,而不添加任何新的内容。在另一种方法中,将
A
的实例作为
B
中的一个插槽,我需要编写简单的方法包装器来在该插槽上调用这些方法

我能想到的唯一方法是:在辅助构造函数中分解对象
A
,并将相应的槽传递给
B
生成实例,即:

(defun make-b (b-1 a-obj)
  (with-slots (a-1 a-2) a-obj
    (make-instance 'b :b-1 b-1 :a-1 a-1 :a-2 a-2)))

有更好的方法吗?(或者,这种方法会导致非常糟糕的设计,我应该完全避免吗?

我不认为有一个通用的解决方案。考虑:例如,如果类
A
有一些插槽,这些插槽不是简单地从一些
:initarg
初始化的,而是在
初始化实例
共享初始化
期间初始化的,那么应该发生什么

也就是说,只要你控制所有相关的类,你就可以尝试

  • 制定一个由
    a
    实现的协议,类似于

    (defgeneric initargs-for-copy (object)
      (:method-combination append)
      (:method append (object) nil))
    
    (defmethod initargs-for-copy append ((object a))
      (list :a-1 (slot-value object 'a-1) :a-2 (slot-value object 'a-2)))
    
    (defun make-b (b-1 a-obj)
      (apply #'make-instance 'b :b-1 b-1 (initargs-for-copy a-obj)))
    
  • 在运行时使用MOP提取插槽(这可能需要了解您选择的Lisp实现,或者需要一些库的帮助,如
    closer MOP
    可通过获取)

  • 用于将
    A
    实例转换为
    B
    实例 破坏性的


不管怎样:我不确定您的用例是否真的保证了继承。这里的合成方法(从设计的角度)似乎更清晰。除了让
B
通过
A
继承一些通用方法实现之外:
B
的实例在您的实际应用中是否真的被认为是
A
的合适实例(即,是否存在
is-A?
关系)?或者您只是想避免在这里提供包装器?

您可以使用组合作为原型继承的一种形式,其中对象从另一个实例“继承”

(defclass prototype-mixin ()
  ((parent :initarg :parent :initform nil :accessor parent)))

(defmethod slot-unbound (c (p prototype-mixin) slot)
  (declare (ignore c))
  (let ((parent (parent p)))
    (if parent
      (slot-value parent slot)
      (call-next-method))))
现在,您定义了两个类:

(defclass a ()
  ((slot :initarg :slot)))

(defclass b (a prototype-mixin) 
  ((other :initarg :other)))
当您从
a
的现有实例创建
b
时,您将
b
parent
槽设置为
a
。由于
b
也是
a
,因此
b
中有一个未绑定的
插槽。当您尝试访问此插槽时,您将访问“父”对象中存在的插槽,它是
a
的一个实例。但如果需要,可以覆盖
b
中的值


这种方法的灵感来自comp.lang.lisp上的Erik Naggum。

您还可以使用A的实例,将其类更改为B,然后初始化添加的插槽。请注意,您确实需要摆脱“A实现方法”的想法。这是CLOS,类/槽和泛型函数/方法在这里更加独立。@RainerJoswig更改类的想法很有趣。但我不确定我是否完全理解类/槽和泛型函数/方法之间的关系。我意识到它们是相当独立的实体。“实现方法”思维的替代方法是什么?@mobiuseng在clos中,并不是“实现方法f”,而是“在通用函数f上定义了一个方法,专门用于A”。当你在多个参数上专门化方法时,这是一个更明显的区别。Joshua是对的。加上:“在泛型函数f上定义了一些方法,专门用于a”。它可能不止一个。@RainerJoswig好的,我明白了。只是在这个特殊情况下,我专门化了一个参数,所以看起来像是“实现方法”“.CLOS的方式转移了很多注意力。谢谢你的回答!我没有想到非平凡的初始化。。。我试图确定构建块之间的关系,以便正确地组合它们。于是,我想到了这个想法。到目前为止,组合似乎比继承更有效。
(defclass a ()
  ((slot :initarg :slot)))

(defclass b (a prototype-mixin) 
  ((other :initarg :other)))