Skip to content

LISP - 类

Common Lisp 中的现代面向对象编程:Common Lisp 对象系统 (CLOS)

Section titled “Common Lisp 中的现代面向对象编程:Common Lisp 对象系统 (CLOS)”

虽然 Common Lisp 早于面向对象编程(OOP)的主流应用,但它拥有有史以来设计最强大、最灵活的对象系统之一:Common Lisp 对象系统 (CLOS)。本章将介绍如何在现代 Lisp 风格中定义类、创建对象以及使用方法。

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)))

通用函数 make-instance 用于创建并返回一个类的新对象(即实例)。最佳实践是使用 defclass 中定义的 :initarg 关键字来提供初始值。

(make-instance class-name &key initarg1 value1 initarg2 value2 ...)

此示例演示了如何创建 box 实例并使用其访问器函数与其槽位进行交互。将此代码保存为 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 10
Breadth of the Box is 20
Height of the Box is 5
New height of the Box is 7

在 CLOS 中,行为通过**通用函数(generic functions)和方法(methods)**定义。通用函数定义名称和参数签名,而方法则提供实际的实现,专门针对不同类的参数。defmethod 宏用于定义方法。

让我们添加一个 volume 方法。与其添加一个 volume 槽位(如果尺寸改变会过时),我们不如按需计算它。这是一种更健壮的设计。

;; 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 10
Breadth of the Box is 10
Height of the Box is 5
Volume of the Box is 500

继承允许一个新类(子类)基于一个现有类(父类),并继承其槽位和方法。子类可以添加新的槽位和方法,或者特化现有的槽位和方法。

让我们创建一个 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 10
Volume of the Weighted Box is 500
Weight of the Weighted Box is 2500
Density of the Weighted Box is 5.0