【问题标题】:How can I pass a super-class object to a sub-class constructor?如何将超类对象传递给子类构造函数?
【发布时间】:2016-02-03 11:09:23
【问题描述】:

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

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

还有继承自A的类B

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

有没有更好的方法来做到这一点? (或者,这种方法可能会导致非常糟糕的设计,我应该完全避免它?)

【问题讨论】:

  • 您还可以获取 A 的实例并将其类更改为 B - 然后初始化添加的插槽。请注意,您确实需要摆脱“A 实现方法”的想法。这是 CLOS,其中类/槽和泛型函数/方法更加独立。
  • @RainerJoswig 改变班级的想法很有趣。但我不确定我是否完全理解你对类/槽和通用函数/方法之间的关系。我意识到它们是非常独立的实体。什么是“实现方法”思维的替代方案?
  • @mobiuseng 在 clos 中,并不是说“a 实现了方法 f”,而是“在泛型函数 f 上定义了一个专门用于 a 的方法”。当您将方法专门用于多个参数时,这是一个更明显的区别。
  • 约书亚是对的。另外:“在泛型函数 f 上定义了一些方法,专门用于 a'。它可能不止一个。
  • @RainerJoswig 好的,我明白了。只是在这种特殊情况下,我只专注于一个参数,所以它看起来像“实现方法”。 CLOS的方式转移了很多焦点。

标签: lisp common-lisp clos


【解决方案1】:

我不认为有一个通用的解决方案。考虑一下:例如,如果类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 实现,或者需要一些库的帮助,例如通过 quicklisp 获得的 closer-mop

    (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? 关系)?还是您只是想避免在此处提供包装器?

【讨论】:

  • 感谢您的回答!我没有考虑过重要的初始化......我正在尝试确定构建块之间的关系以正确组合它们。于是,我想到了这个主意。到目前为止,组合似乎比继承更有效。
【解决方案2】:

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

(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。当您尝试访问此插槽时,您将访问“父”对象中存在的一个,它是a 的一个实例。但如果你愿意,你可以覆盖 b 中的值。

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

【讨论】:

    猜你喜欢
    • 2016-04-21
    • 2015-04-26
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多