Common Lisp类槽的访问器函数

时间:2016-01-11 01:55:14

标签: class common-lisp

我目前正在读Peter Seibel的Practical Common Lisp中关于课程的章节,我对使用accessor functions感到困惑。

SETF

我不理解下面给出的涉及银行账户和客户名称的setf函数的新定义:

(defun (setf customer-name) (name account)
  (setf (slot-value account 'customer-name) name))

使用如下:

(setf (customer-name my-account) "Sally Sue")

为什么setf的定义采用两个参数(name account),但这不是我们提供的?上面的customer-name函数是否与稍后定义的customer-name读者函数有关(见下文)?

(defgeneric (setf customer-name) (value account))

(defmethod (setf customer-name) (value (account bank-account))
  (setf (slot-value account 'customer-name) value))

直接插槽访问

访问者功能的动机是避免直接访问插槽;接口实现和所有这些。但Common Lisp提供了:reader:writer:accessor个插槽选项来实现这一目标。 E.g。

(customer-name
 :initarg :customer-name
 :initform (error "Must supply a customer name.")
 :accessor customer-name)

我是否正确理解这应该仅用于直接访问绝对正常的插槽?因为如果我们稍后决定不直接访问插槽 ,我们就会破坏。

2 个答案:

答案 0 :(得分:7)

setf有点神奇。 setf的要点是设置一个类似于访问该值的语法的语法。对于使用setf(或defun)定义defmethod函数的情况,(setf (f ...) val)等同于(funcall #'(setf f) val ...)。这就是setf在你的例子中只接受一个参数的原因;传递给(setf customer-name)的第二个参数是my-account。如果您想了解更多关于setf内部的信息,我写了一篇关于它的博客文章,您可以找到here

由于将读者和作者写入广告位是如此常见,defclass提供了:reader:writer:accessor选项。当您传递其中一个选项时,defclass将自动编写相应的读者和作者。例如,槽定义:

(customer-name
  :accessor customer-name
  ...)

会自动编写代码:

(defgeneric customer-name (account))

(defmethod customer-name ((account bank-account))
  (slot-value account 'customer-name))

(defgeneric (setf customer-name) (value account))

(defmethod (setf customer-name) (value (account bank-account))
  (setf (slot-value account 'customer-name) value))

给你!

关于直接访问插槽的问题,我看到的最常见的模式是为所有插槽创建访问器,然后只导出应该可以直接访问的插槽的访问器。这样,您可以直接从与定义的类相同的包中访问所有插槽,但是从另一个包中,您只能访问"导出的"槽。

答案 1 :(得分:2)

作为设定者的功能

(defun (setf customer-name) (name account)
  (setf (slot-value account 'customer-name) name))

(setf customer-name)实际上是函数名称。是的,在Common Lisp中,函数名称可以是这样的列表 - 不仅仅是符号。参数是新name和客户account。然后,它会在customer-name实例中设置account个插槽。

(setf (customer-name my-account) "Sally Sue")

因此,您使用访问者表单,在其周围写一个setf和新值。然后Common Lisp会找出哪个setter属于accessor表单。这是你之前定义的功能。 my-account是对象,"Sally Sue"是新的customer-name

作为setter的通用函数

(defgeneric (setf customer-name) (value account))

这定义了类似于上面的功能,但这次是CLOS 泛型函数而不是defun定义的普通函数。 Common Lisp具有普通函数和泛型函数。后者是 Common Lisp对象系统的一部分,并提供动态多分派等功能。此通用函数将替换任何其他函数定义。

(defmethod (setf customer-name) (value (account bank-account))
  (setf (slot-value account 'customer-name) value))

这定义了一个方法,类似于上面的函数,但这次它是一个名为(setf customer-name)的泛型函数的方法 - 记住这个列表是一个函数名。此方法是针对帐户的所有value类和bank-account类定义的。因此,您只能使用它设置银行帐户(及其子类)的客户名称。

在DEFCLASS中定义的Setter

正如您所提到的,DEFCLASS可以通过提供适当的插槽选项来定义setter。这只是一个方便,因此一个类的定义可以在一个地方。

信息隐藏和其他软件工程原则

实际使用 setter功能的时间和地点,以及使用直接插槽访问时,只需要样式和软件工程原理。 Common Lisp没有任何限制。由您来定义保持软件可维护性的规则。您还可以遵循面向对象软件设计的常规最佳实践。请注意,在Common Lisp中,很多是样式和约定,因为Common Lisp没有真正的模块系统。它有符号包,但这不是一个真正的模块系统,因为它没有为类,槽和/或泛型函数定义的可见范围。