LISP - 类
Common Lisp 中的现代面向对象编程:Common Lisp 对象系统 (CLOS)
Section titled “Common Lisp 中的现代面向对象编程:Common Lisp 对象系统 (CLOS)”虽然 Common Lisp 早于面向对象编程(OOP)的主流应用,但它拥有有史以来设计最强大、最灵活的对象系统之一:Common Lisp 对象系统 (CLOS)。本章将介绍如何在现代 Lisp 风格中定义类、创建对象以及使用方法。
使用 defclass 定义类
Section titled “使用 defclass 定义类”defclass 宏是 CLOS 的基石。它定义了一个新类,作为创建对象的模板。其通用语法是:
(defclass class-name (superclass-name*) (slot-description*) class-option*)class-name:新类的名称。superclass-name*:此新类将继承的父类列表。这支持多重继承。slot-description*:将为此类的实例存储数据的槽位(成员变量或字段)列表。
每个槽位都由名称和可选的槽位选项定义,这些选项控制其行为。最重要的选项包括:
:accessor function-name:创建一个既能读取又能写入槽位值的函数。:reader function-name:创建一个只能读取槽位值的函数。:writer function-name:创建一个只能写入槽位值的函数。:initarg :keyword-name:指定一个关键字,可与make-instance一起使用来初始化槽位值。:initform expression:如果槽位未通过其他方式初始化,则为其提供默认值。
让我们定义一个 Box 类,包含长度、宽度和高度,并使用现代最佳实践进行访问和初始化。
;; 一个定义良好的 Box 类,包含访问器、初始参数和默认值。(defclass box () ((length :accessor box-length :initarg :length :initform 1) (breadth :accessor box-breadth :initarg :breadth :initform 1) (height :accessor box-height :initarg :height :initform 1)))创建类的实例
Section titled “创建类的实例”通用函数 make-instance 用于创建并返回一个类的新对象(即实例)。最佳实践是使用 defclass 中定义的 :initarg 关键字来提供初始值。
(make-instance class-name &key initarg1 value1 initarg2 value2 ...)示例:创建和检查一个 Box
Section titled “示例:创建和检查一个 Box”此示例演示了如何创建 box 实例并使用其访问器函数与其槽位进行交互。将此代码保存为 main.lisp。
main.lisp 文件
Section titled “main.lisp 文件”;; 定义 Box 类(如上所示)(defclass box () ((length :accessor box-length :initarg :length :initform 1) (breadth :accessor box-breadth :initarg :breadth :initform 1) (height :accessor box-height :initarg :height :initform 1)))
;; 使用 initargs 创建 'box' 类的一个实例。;; 这是现代推荐的方式。(defparameter *my-box* (make-instance 'box :length 10 :breadth 20 :height 5))
;; 使用访问器函数访问并打印槽位值。(format t "Length of the Box is ~d~%" (box-length *my-box*))(format t "Breadth of the Box is ~d~%" (box-breadth *my-box*))(format t "Height of the Box is ~d~%" (box-height *my-box*))
;; 我们也可以使用 `setf` 和访问器来修改槽位的值。(setf (box-height *my-box*) 7)(format t "New height of the Box is ~d~%" (box-height *my-box*))Length of the Box is 10Breadth of the Box is 20Height of the Box is 5New height of the Box is 7定义通用函数和方法
Section titled “定义通用函数和方法”在 CLOS 中,行为通过**通用函数(generic functions)和方法(methods)**定义。通用函数定义名称和参数签名,而方法则提供实际的实现,专门针对不同类的参数。defmethod 宏用于定义方法。
让我们添加一个 volume 方法。与其添加一个 volume 槽位(如果尺寸改变会过时),我们不如按需计算它。这是一种更健壮的设计。
main.lisp 文件(更新后)
Section titled “main.lisp 文件(更新后)”;; Box 类(无需改动)(defclass box () ((length :accessor box-length :initarg :length :initform 1) (breadth :accessor box-breadth :initarg :breadth :initform 1) (height :accessor box-height :initarg :height :initform 1)))
;; 定义一个专门用于 `box` 类对象的 `volume` 方法。(defmethod volume ((b box)) "计算盒子体积。" ;; `with-slots` 提供了一种通过名称访问槽位的简洁方式, ;; 无需重复调用访问器函数。 (with-slots (length breadth height) b (* length breadth height)))
;; 创建一个实例(defparameter *my-box* (make-instance 'box :length 10 :breadth 10 :height 5))
;; 显示尺寸和计算出的体积(format t "Length of the Box is ~d~%" (box-length *my-box*))(format t "Breadth of the Box is ~d~%" (box-breadth *my-box*))(format t "Height of the Box is ~d~%" (box-height *my-box*))(format t "Volume of the Box is ~d~%" (volume *my-box*))Length of the Box is 10Breadth of the Box is 10Height of the Box is 5Volume of the Box is 500继承允许一个新类(子类)基于一个现有类(父类),并继承其槽位和方法。子类可以添加新的槽位和方法,或者特化现有的槽位和方法。
示例:一个 Weighted-Box
Section titled “示例:一个 Weighted-Box”让我们创建一个 weighted-box,它继承自 box 并添加一个 weight 槽位和一个 density 方法。
main.lisp 文件(更新,包含继承)
Section titled “main.lisp 文件(更新,包含继承)”;; 基类(defclass box () ((length :accessor box-length :initarg :length :initform 1) (breadth :accessor box-breadth :initarg :breadth :initform 1) (height :accessor box-height :initarg :height :initform 1)))
(defmethod volume ((b box)) (with-slots (length breadth height) b (* length breadth height)))
;; 派生类 'weighted-box' 继承自 'box'(defclass weighted-box (box) ((weight :accessor box-weight :initarg :weight :initform 0)))
;; 专门用于 'weighted-box' 的新方法(defmethod density ((wb weighted-box)) "计算一个加权盒子的密度。" (when (plusp (volume wb)) (/ (box-weight wb) (volume wb))))
;; 创建派生类的一个实例(defparameter *my-weighted-box* (make-instance 'weighted-box :length 10 :breadth 10 :height 5 :weight 2500))
;; 显示值(format t "Length of the Weighted Box is ~d~%" (box-length *my-weighted-box*))(format t "Volume of the Weighted Box is ~d~%" (volume *my-weighted-box*)) ;; 继承的方法(format t "Weight of the Weighted Box is ~d~%" (box-weight *my-weighted-box*))(format t "Density of the Weighted Box is ~f~%" (density *my-weighted-box*))Length of the Weighted Box is 10Volume of the Weighted Box is 500Weight of the Weighted Box is 2500Density of the Weighted Box is 5.0