如何将 super-class 对象传递给 sub-class 构造函数?

How can I pass a super-class object to a sub-class constructor?

假设我有 class A 和几个插槽:

(defclass a ()
  ((a-1 :initarg :a-1)
   (a-2 :initarg :a-2)))

和继承自A的classB

(defclass b (a)
  ((b-1 :initarg :b-1)))

如果我想实例化 Bmake-instance 将为我提供插槽 :a-1:a-2:b-1

这是一个疯狂的想法:如果我想使用 A 的现有实例实例化 B 并且只填充插槽 b-1 怎么办?

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

我能想到的唯一方法:在辅助构造函数中分解对象A并将相应的插槽传递给make-instance for 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)))

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

我认为没有通用的解决方案。考虑:应该发生什么,例如,如果 class A 有一些插槽,这些插槽不是简单地从某些 :initarg 初始化的,而是说,在 initialize-instanceshared-initialize?

就是说,只要您能控制所有涉及的 classes,您就可以尝试

  • 制定一个由 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 可通过 quicklisp)

    (defun list-init-args (object)
      (let* ((class (class-of object))
             (slots (closer-mop:class-slots class)))
        (loop
          for slot in slots
          for name = (closer-mop:slot-definition-name slot)
          for keyword = (closer-mop:slot-definition-initargs slot)
          when (and keyword (slot-boundp object name))
            nconc (list (car keyword) (slot-value object name)))))
    
    (defun make-b (b-1 a-obj)
       (apply #'make-instance 'b :b-1 b-1 (list-init-args a-obj)))
    
  • 使用change-classA实例变身为B实例 破坏性地。

无论如何:我不确定您的用例是否真的需要继承。组合方法在这里(从设计的角度来看)似乎更加清晰。除了让 B 通过 A 继承一些通用方法实现之外:B 的实例在您的实际应用程序中是否真的被认为是 A 的正确实例(即,是否有 is-a?关系)?或者您只是想避免必须在此处提供包装器?

您尝试做的事情可以使用组合作为一种原型继承的形式来完成,其中一个对象 "inherits" 来自另一个实例。

(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 时,您将 bparent 槽设置为 a。由于 b 也是一个 a,因此 b 中有一个未绑定的 slot。当您尝试访问此插槽时,您会访问 "parent" 对象中存在的插槽,它是 a 的一个实例。但如果需要,您可以覆盖 b.

中的值

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