在 Lisp CLOS 中,类设置器如何自动更新另一个槽?

Tod*_*odd 3 lisp common-lisp clos

我是 CLOS 新手。这是我的例子:

   (defclass box ()
    ((length :accessor box-length :initform 0 :initarg :bxl)
     (breath :accessor box-breadth :initform 0 :initarg :bxb)
     (height :accessor box-height :initform 0 :initarg :bxh)
     (volume :reader   volume     :initform 0 :initarg :v)))
Run Code Online (Sandbox Code Playgroud)

构造函数是:

    (defun make-box (l b h)
     (make-instance 'box :bxl l :bxb b :bxh h :v (* l b h)))
Run Code Online (Sandbox Code Playgroud)

所以当我像这样创建“盒子”的实例时:

    ; make a box, 4 x 3 x 2
    (defparameter my-box (make-box 4 3 2))`
Run Code Online (Sandbox Code Playgroud)

它按我的预期工作。我可以“描述”我的盒子并得到:

    (describe my-box)
      #<BOX {100363F493}>
        [standard-object]

    Slots with :INSTANCE allocation:
      LENGTH                         = 4
      BREATH                         = 3
      HEIGHT                         = 2
      VOLUME                         = 24
Run Code Online (Sandbox Code Playgroud)

现在,问题来了。如果我像这样更新“高度”:

    (setf (box-height my-box) 5)
Run Code Online (Sandbox Code Playgroud)

如何让这个“setf”自动更新“volume”插槽?

那么 VOLUME 将更改为(* 4 3 5) = 60?

ign*_*ens 7

实现此目的的一种方法是在setf各种访问器的方法上使用 after 方法。所以:

(defmethod (setf box-length) :after (length (b box))
  (with-slots (breadth height volume) b
    (setf volume (* length breadth height))))
Run Code Online (Sandbox Code Playgroud)

这也可以通过 before 方法来完成,但如果您使用通用的“更新卷”函数,您需要使用 after 方法来避免存储插槽两次,或者setf完全自己定义访问器的侧面。

另一种当然更简单的方法是根本没有卷槽,而是计算它:

(defclass box ()
  ((length :accessor box-length :initform 0 :initarg :bxl)
   (breath :accessor box-breadth :initform 0 :initarg :bxb)
   (height :accessor box-height :initform 0 :initarg :bxh)))

(defgeneric volume (object))

(defmethod volume ((b box))
  (* (box-length b) (box-breadth b) (box-height b)))
Run Code Online (Sandbox Code Playgroud)

显然,其他类仍然可以拥有卷槽,并且volume通用函数上的方法可以访问该槽:协议是相同的。

您甚至可以通过为esdescribe定义方法或仅定义 after 方法来报告体积。特别是在后一种情况下,您可能必须努力使格式与您的实现的行为一致。这是一个恰好适合我通常的实现(LispWorks)的方法:describe-objectboxdescribe

(defmethod describe-object :after ((b box) stream)
  (format stream "~& and volume ~D~%" (volume b)))
Run Code Online (Sandbox Code Playgroud)

现在

> (describe (make-instance 'box))

#<box 801001147B> is a box
length      0
breath      0
height      0
 and volume 0
Run Code Online (Sandbox Code Playgroud)