如何将 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)))
如果我想实例化 B
,make-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-instance
或 shared-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-class
将A
实例变身为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
时,您将 b
的 parent
槽设置为 a
。由于 b
也是一个 a
,因此 b
中有一个未绑定的 slot
。当您尝试访问此插槽时,您会访问 "parent" 对象中存在的插槽,它是 a
的一个实例。但如果需要,您可以覆盖 b
.
中的值
这种方法的灵感来自 Erik Naggum 在 comp.lang.lisp 上的 a post。
假设我有 class A
和几个插槽:
(defclass a ()
((a-1 :initarg :a-1)
(a-2 :initarg :a-2)))
和继承自A
的classB
:
(defclass b (a)
((b-1 :initarg :b-1)))
如果我想实例化 B
,make-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-instance
或 shared-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-class
将A
实例变身为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
时,您将 b
的 parent
槽设置为 a
。由于 b
也是一个 a
,因此 b
中有一个未绑定的 slot
。当您尝试访问此插槽时,您会访问 "parent" 对象中存在的插槽,它是 a
的一个实例。但如果需要,您可以覆盖 b
.
这种方法的灵感来自 Erik Naggum 在 comp.lang.lisp 上的 a post。