4

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

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

B继承自的类A

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

如果我想实例化B,会make-instance提供给我插槽:a-1和.:a-2:b-1

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

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

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

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

4

2 回答 2

4

我不认为,有一个通用的解决方案。考虑一下:例如,如果类A有一些槽,它们不是简单地从 some 初始化:initarg,而是在initialize-instanceor期间初始化,应该发生什么shared-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-class将实例破坏性地变形为A实例B

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

于 2016-02-03T11:23:30.130 回答
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 时a,您将 的parent插槽设置ba。既然b也是一个a,就有一个无界 slotb。当您尝试访问此插槽时,您将访问存在于“父”对象中的那个,它是a. 但如果你愿意,你可以覆盖b.

这种方法的灵感来自 Erik Naggum 在 comp.lang.lisp 上的一篇文章。

于 2016-02-03T16:59:36.423 回答