【问题标题】:Separating initialization arguments and class slots in Common Lisp Object System for making objects在 Common Lisp 对象系统中分离初始化参数和类槽以创建对象
【发布时间】:2021-12-18 00:10:59
【问题描述】:

This 询问如何从其他插槽初始化插槽。我想要实现的是将一些参数作为输入——也许但不一定是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。然而,由于 after 方法是以最不具体​​的第一顺序调用的,这意味着超类插槽将在当前类插槽之前初始化。另一方面,看起来更常见的是,一个人将接受构造当前类的参数,并在这些参数的基础上初始化超类插槽。

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

还有其他方法吗?

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

【问题讨论】:

    标签: oop common-lisp clos


    【解决方案1】:

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

    【讨论】:

    • 现在正在考虑 Lisp 外星人标志
    • 感谢您的详细回复! Slots defined in superclasses don't generally depend for their values on subclass slots 我想我应该强调这基本上构成了我的情况。继续这个例子,考虑一个复杂可怕的怪物,它可以有不同种类的眼睛和腿,简单可怕的怪物是它的子类,因此它只有一种眼睛和腿;在这种情况下,我想在子类插槽的基础上初始化超类插槽。或者还有其他更好的方式来思考这个问题?
    • @digikar:您所描述的内容听起来像是一个设计问题,但是如果您有两个插槽可以在超类中独立但在某些子类中应该相同,那么在子类中定义, 一个通用的 initarg,或者在 initialize-instance 的 after 方法中从另一个初始化一个。
    • @ignisvolens 更多的是超类中不同项目的列表与子类问题中相同项目的列表。但即便如此,我想重新定义子类中的超类插槽,并在子类的 initialize-instance 方法中进行初始化,让我失去了超类的 initialize-instance 方法中存在的验证代码。我最好的选择是将验证代码分离到一个单独的函数中吗?或者是否有一种 CLOS-sy 方法可以在没有单独功能的情况下重用验证代码?
    • @digikar:正如我上面所说,你有一个设计问题:CLOS 不会为你解决这个问题,我也不会为你解决它。
    猜你喜欢
    • 2015-01-20
    • 1970-01-01
    • 1970-01-01
    • 2011-06-03
    • 1970-01-01
    • 1970-01-01
    • 2015-04-21
    • 1970-01-01
    • 2020-09-01
    相关资源
    最近更新 更多