在Common Lisp对象系统中分离初始化参数和类插槽以创建对象。

3

这个问题询问如何从其他插槽初始化插槽。而我想要实现的是将一些参数作为输入 - 可能但不一定是通过make-instance - 并将这些参数转换为类插槽以进行存储。实际上,我想将类的实现与其(初始化)接口分离。

有没有推荐的方法来实现这一点?

我能想到的最简单的方法就是创建一个(defun make-my-object ...)作为接口。然后可能使用适当的参数调用make-instance

例如,想象一下

(defclass my-object () (slot-1 slot-2))
(defun make-my-object (arg-1 arg-2)
  (make-instance 'my-object 
                 :slot-1 (+ arg-1 arg-2) 
                 :slot-2 (- arg-1 arg-2)))

我能想到的其他方式包括实现一个initialize-instance :after,它以关键字参数arg-1arg-2作为输入并适当地初始化slot-1slot-2。然而,由于在least-specific-first-order中调用了after方法,这意味着超类的slots将在当前类的slots之前初始化。另一方面,更常见的做法是为构建当前类取参数,并根据这些参数初始化超类slots。

另一种选择是initialize-instance :before或者也可以使用:around,但如果继承层次结构中有多个类具有这样的"interface-implementation"差异,除非我可以向call-next-method传递参数,否则我看不出来这会起作用。

还有其他方法吗?

编辑:感谢@ignis volens提醒我,我的一个主要关注点是超类slots从子类slots初始化。有推荐的方法吗?

1个回答

4

我不确定我理解你的问题。答案几乎肯定是在initialize-instance方法之后。你说这会导致在超类中定义的插槽先被初始化:是的,它会,而且几乎肯定是你想要发生的。通常,超类中定义的插槽不依赖于子类插槽的值(总有例外),因此按照最少特定性优先顺序进行初始化几乎总是你想要的。

我使用的两种初始化插槽的常见方法之一是仅在定义中声明它们的initargs:

(defclass minibeast ()
  ((legs :initform 'uncountable
         :initarg :legs
         :initarg :leg-count
         :accessor legs)
   (tentacles :initform 'many
              :initarg :tentacles
              :initarg :number-of-tentacles
              :accessor tentacles)))

现在,(make-instance 'minibeast :legs 87)可以按预期工作。这样做是有效的(因为显然如果两个插槽在不同的类中定义,则必须这样做):

(defclass awful-monster ()
  ((legs :initform 'uncountable
         :initarg :legs
         :initarg :leg-count
         :accessor legs)
   (appendages :initform 'many
               :initarg :legs
               :initarg :appendages)))

现在,(make-instance 'awful-monster :legs 93)将生成一只有93条腿和93个附属物的可怕怪物。

然而,这种方法可能不符合将接口与实现分离的要求。您可能还想在初始化槽时执行一些计算。在这两种情况下,通常在initialize-instance方法之后是正确的方法:

(defclass horrible-monster ()
  ((legs :initform 983
         :accessor legs)
   (eyes :initform 63
         :accessor eyes)
   (appendages
    :reader appendages)))
   
(defmethod initialize-instance :after
  ((m horrible-monster) &key eyes legs (stalky-eyes t))
  (with-slots ((e eyes) (l legs) appendages) m
    (when eyes (setf e eyes))
    (when legs (setf l legs))
    (setf appendages (if stalky-eyes (+ e l) l))))

现在,可怕的怪物将获得适当数量的附肢(我不确定为什么可怕的怪物不知道它们的眼睛是否在柄上:也许它们没有镜子)。

当然,还有其他许多组合。您可能不想让用户代码显式调用make-instance,而是将其封装在某个函数中:

(defun make-awful-thing (&rest args &key (sort-of-horrible-thing 'horrible-monster)
                               &allow-other-keys)
  (let ((the-remaining-args (copy-list args)))
    ;; No doubt alexandria or something has a way of doing this
    (remf the-remaining-args ':sort-of-horrible0thing)
    (apply #'make-instance sort-of-horrible-thing the-remaining-args)))

现在,您当然可以轻松地拥有一些定制的初始化协议:

(defgeneric enliven-horrible-thing (horrible-thing &key)
  (:method :around ((horrible-thing t) &key)
   (call-next-method)
   t))

(defun make-awful-thing (&rest args &key (sort-of-horrible-thing 'horrible-monster)
                               &allow-other-keys)
  (let ((the-remaining-args (copy-list args)))
    ;; No doubt alexandria or something has a way of doing this
    (remf the-remaining-args ':sort-of-horrible0thing)
    (apply #'enliven-horrible-thing
           (apply #'make-instance sort-of-horrible-thing
                  the-remaining-args)
           the-remaining-args)))

(defmethod enliven-horrible-thing ((horrible-thing horrible-monster)
                                   &key (ichor t) (smell 'unspeakable))
  ...)

3
我现在正在思考 Lisp 外星人标志。 - coredump
感谢您详细的回复! “在超类中定义的插槽通常不依赖于子类插槽的值。” 我认为我应该强调这基本上构成了我的案例。继续上面的例子,考虑一个可以有不同种类的眼睛和腿的复杂可怕的怪物,简单可怕的怪物是它的子类,只有一种眼睛和腿;在这种情况下,我希望根据子类插槽初始化超类插槽。还是有其他更好的思路吗? - digikar
@digikar:你所描述的问题似乎是一个设计问题,但如果在超类中有两个可以独立的插槽,在某些子类中应该是相同的,那么可以在子类中定义一个共同的initarg,或者在initialize-instance的后续方法中从一个初始化另一个。 - ignis volens
@digikar:你并没有重新定义插槽,而是扩展了它们的定义。所有超类中的initialize-instance方法将会做他们原本就该做的事情:这就是为什么它们是后续方法。我建议你实际尝试一些例子(放置调用print或其他你想要的内容来查看发生了什么),而不是简单地猜测可能会发生什么。 - ignis volens
1
@dikigar:就像我之前所说的,你有一个设计问题:CLOS 无法为你解决这个问题,而我也不会替你解决。 - ignis volens
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接