Common Lisp class 槽的访问函数
Accessor functions for Common Lisp class slots
我目前正在阅读 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
reader函数有什么关系吗(见下文)?
(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
插槽选项来做到这一点。例如
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name)
我的理解是否正确,这应该只用于绝对可以直接访问的插槽?因为如果我们稍后决定插槽 不应该 直接访问,我们会破坏事情。
setf
有点神奇。 setf
的要点是使设置值的语法类似于访问该值的语法。对于使用 defun
(或 defmethod
)定义 setf
函数的情况,(setf (f ...) val)
等同于 (funcall #'(setf f) val ...)
。这就是为什么 setf
在您的示例中只接受一个参数;传递给 (setf customer-name)
的第二个参数是 my-account
。如果您想了解更多关于 setf
的内部信息,我写了一篇关于它的博客 post,您可以找到 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))
给你!
至于你关于直接访问插槽的问题,我看到的最常见的模式是为所有插槽制作访问器,然后只导出应该可以直接访问的插槽的访问器。这样,您可以直接从与 class 定义相同的包中访问所有插槽,但是从不同的包中您只能访问 "exported" 插槽。
函数作为 setter
(defun (setf customer-name) (name account)
(setf (slot-value account 'customer-name) name))
(setf customer-name)
其实就是函数名。是的,在 Common Lisp 中,函数名可以是这样的列表——而不仅仅是符号。参数是新 name
和客户 account
。然后它在 account
实例中设置 customer-name
插槽。
(setf (customer-name my-account) "Sally Sue")
所以你使用访问器形式,在它周围写一个 setf
和新值。 Common Lisp 然后找出哪个 setter 属于访问器形式。这是您之前定义的函数。 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
class 的所有 class 定义的。因此您只能用它来设置银行账户(及其子classes)的客户名称。
DEFCLASS 中定义的设置器
如您所述,DEFCLASS
可以通过提供适当的插槽选项来定义 setter。这只是一种方便,所以 class 的大部分定义都可以放在一个地方。
信息隐藏和其他软件工程原理
何时何地实际使用setter函数以及何时使用直接插槽访问只是风格问题和软件工程原理。 Common Lisp 没有任何限制。由您来定义保持软件可维护性的规则。您还可以遵循 object-oriented 软件设计的常用最佳实践。请注意,在 Common Lisp 中,更多的是风格和约定,因为 Common Lisp 没有真正的模块系统。它有符号包,但这不是真正的模块系统,因为它没有为 classes、插槽 and/or 通用函数定义可见性范围。
我目前正在阅读 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
reader函数有什么关系吗(见下文)?
(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
插槽选项来做到这一点。例如
(customer-name
:initarg :customer-name
:initform (error "Must supply a customer name.")
:accessor customer-name)
我的理解是否正确,这应该只用于绝对可以直接访问的插槽?因为如果我们稍后决定插槽 不应该 直接访问,我们会破坏事情。
setf
有点神奇。 setf
的要点是使设置值的语法类似于访问该值的语法。对于使用 defun
(或 defmethod
)定义 setf
函数的情况,(setf (f ...) val)
等同于 (funcall #'(setf f) val ...)
。这就是为什么 setf
在您的示例中只接受一个参数;传递给 (setf customer-name)
的第二个参数是 my-account
。如果您想了解更多关于 setf
的内部信息,我写了一篇关于它的博客 post,您可以找到 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))
给你!
至于你关于直接访问插槽的问题,我看到的最常见的模式是为所有插槽制作访问器,然后只导出应该可以直接访问的插槽的访问器。这样,您可以直接从与 class 定义相同的包中访问所有插槽,但是从不同的包中您只能访问 "exported" 插槽。
函数作为 setter
(defun (setf customer-name) (name account) (setf (slot-value account 'customer-name) name))
(setf customer-name)
其实就是函数名。是的,在 Common Lisp 中,函数名可以是这样的列表——而不仅仅是符号。参数是新 name
和客户 account
。然后它在 account
实例中设置 customer-name
插槽。
(setf (customer-name my-account) "Sally Sue")
所以你使用访问器形式,在它周围写一个 setf
和新值。 Common Lisp 然后找出哪个 setter 属于访问器形式。这是您之前定义的函数。 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
class 的所有 class 定义的。因此您只能用它来设置银行账户(及其子classes)的客户名称。
DEFCLASS 中定义的设置器
如您所述,DEFCLASS
可以通过提供适当的插槽选项来定义 setter。这只是一种方便,所以 class 的大部分定义都可以放在一个地方。
信息隐藏和其他软件工程原理
何时何地实际使用setter函数以及何时使用直接插槽访问只是风格问题和软件工程原理。 Common Lisp 没有任何限制。由您来定义保持软件可维护性的规则。您还可以遵循 object-oriented 软件设计的常用最佳实践。请注意,在 Common Lisp 中,更多的是风格和约定,因为 Common Lisp 没有真正的模块系统。它有符号包,但这不是真正的模块系统,因为它没有为 classes、插槽 and/or 通用函数定义可见性范围。