我正在阅读Peter Seibel的Practical Common Lisp中有关类的章节,我对使用访问器函数感到困惑.
我不理解setf下面给出的涉及银行账户和客户名称的功能的新定义:
(defun (setf customer-name) (name account)
(setf (slot-value account 'customer-name) name))
Run Code Online (Sandbox Code Playgroud)
它使用如下:
(setf (customer-name my-account) "Sally Sue")
Run Code Online (Sandbox Code Playgroud)
为什么定义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))
Run Code Online (Sandbox Code Playgroud)
访问器功能的动机是避免直接访问插槽; 接口实现和所有这些.但Common Lisp提供了:reader,:writer和:accessor插槽选项来做到这一点.例如
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name)
Run Code Online (Sandbox Code Playgroud)
我是否正确理解这应该只用于直接访问绝对正常的插槽?因为如果我们稍后决定不应该直接访问插槽,我们就会破坏它们.
setf有点神奇.关键setf是要设置一个类似于访问该值的语法的语法.对于setf使用defun(或defmethod)定义函数的情况,(setf (f ...) val)变为等效(funcall #'(setf f) val ...).这就是为什么setf在你的例子中只需要一个参数; 传递给的第二个参数(setf customer-name)是my-account.如果你想了解更多关于内部的内容setf,我写了一篇关于它的博客文章,你可以在这里找到.
因为它是如此普遍写读者和作家的插槽,defclass提供了:reader,:writer和:accessor选项.当您传入其中一个选项时,defclass将自动编写相应的读者和作者.例如,插槽定义:
(customer-name
:accessor customer-name
...)
Run Code Online (Sandbox Code Playgroud)
会自动编写代码:
(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))
Run Code Online (Sandbox Code Playgroud)
为了你!
至于你直接访问插槽的问题,我看到的最常见的模式是为所有插槽创建访问器,然后只导出应该可以直接访问的插槽的访问器.这样,您可以直接从定义类的同一个包中访问所有插槽,但是从另一个包中,您只能访问"导出"插槽.