我目前正在读Peter Seibel的Practical Common Lisp中关于课程的章节,我对使用accessor functions感到困惑。
我不理解下面给出的涉及银行账户和客户名称的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)
我是否正确理解这应该仅用于直接访问绝对正常的插槽?因为如果我们稍后决定不直接访问插槽 ,我们就会破坏。
答案 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没有真正的模块系统。它有符号包,但这不是一个真正的模块系统,因为它没有为类,槽和/或泛型函数定义的可见范围。