假设我的课程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
来实例化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)))
有更好的方法吗? (或许,这种方法导致设计非常糟糕,我应该完全避免它?)
答案 0 :(得分:4)
我不认为,有一个普遍的解决方案。考虑:应该发生什么,例如,如果类A
有一些插槽,这些插槽不是简单地从某些:initarg
初始化的,而是在initialize-instance
或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
的正确实例(即,是否存在{ {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。