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

时间:2016-02-03 11:09:24

标签: lisp common-lisp clos

假设我的课程A有几个插槽:

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

继承自B

的班级A
(defclass b (a)
  ((b-1 :initarg :b-1)))

如果我要实例化Bmake-instance将为我提供广告时段:a-1:a-2:b-1

这是一个疯狂的想法:如果我想使用B的现有实例并仅填充广告位A来实例化b-1该怎么办?

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

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

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

2 个答案:

答案 0 :(得分:4)

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

也就是说,只要您掌控所有相关课程,就可以尝试

  • 制定由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的正确实例(即,是否存在{ {1}}关系)?或者你只是想避免在这里提供包装?

答案 1 :(得分:3)

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

(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)))

当您从b的现有实例创建a时,您将parent的{​​{1}}广告位设置为b。由于a也是b,因此a中有一个未绑定的slot。当您尝试访问此插槽时,您可以访问" parent"中存在的插槽。 object,是b的一个实例。但如果您愿意,可以覆盖a

中的值

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