首页 > 解决方案 > 在 Common Lisp 对象系统中分离初始化参数和类槽以创建对象

问题描述

询问从其他插槽初始化插槽。我想要实现的是将一些参数作为输入 - 也许但不一定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 :afterarg-1and作为关键字参数并适当arg-2初始化的方法。但是,由于 after 方法以最小特定的一阶调用,这意味着超类插槽将在当前类插槽之前初始化。另一方面,看起来更常见的是,将获取构造当前类的参数,并在这些参数的基础上初始化超类插槽。slot-1slot-2

另一种选择是initialize-instance :before- 或者也是:around- 但是如果层次结构中的多个类具有这样的“接口实现”差异,我认为这不起作用,除非我可以将参数传递给call-next-method.

还有其他方法吗?

编辑:感谢@ignis volens 让我注意到(其中一个)我主要关心的是从子类插槽初始化超类插槽。有推荐的方法吗?

标签: oopcommon-lispclos

解决方案


我不确定我是否理解你的问题。答案几乎可以肯定是在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))
  ...)

推荐阅读