Common Lisp 本身并没有像 Java/C++ 那样内建“class”关键字——它用更灵活也更强大的 CLOS(Common Lisp Object System)来实现面向对象。除了 CLOS,你还可以用更轻量的 defstruct
、甚至闭包来模拟对象。下面分三部分说明。
1. CLOS:真正的类/对象系统
(defclass person ()
((name :initarg :name
:accessor person-name
:documentation "人的姓名")
(age :initarg :age
:accessor person-age
:initform 0
:documentation "年龄"))
(:documentation "一个简单的 Person 类"))
;; 定义泛型函数
(defgeneric greet (obj)
(:documentation "向 obj 打招呼"))
;; 为 person 类定义方法
(defmethod greet ((p person))
(format t "Hello, I am ~A, ~D years old.~%"
(person-name p)
(person-age p)))
;; 使用
(let ((bob (make-instance 'person :name "Bob" :age 42)))
(greet bob)) ; => Hello, I am Bob, 42 years old.
CLOS 特点:
- 多重继承、动态修改类结构
- 多重分派(方法可根据所有参数的类选择)
- 强大的 method combination(
:before/:after/:around
)
- 可在运行时生成/修改类(MOP,meta-object protocol)
2. defstruct:结构体的“轻量”方案
如果你只需要一个简单的、固定字段的记录,可以用 defstruct
:
(defstruct point
x
y)
(let ((p (make-point :x 10 :y 20)))
(format t "X=~A, Y=~A~%"
(point-x p) (point-y p)))
- 自动生成
make-point
、访问器 point-x
/point-y
,还有打印、相等判断等支持
- 不能多重分派,只能配合普通函数使用
3. 闭包/消息传递:最灵活的“对象”
用闭包(closure)模拟私有数据+消息分发,也是一种 Lisp 风格的“对象”:
(defun make-person (name age)
(let ((n name) (a age))
(lambda (msg &rest args)
(ecase msg
(:get-name n)
(:get-age a)
(:set-name (setf n (first args)))
(:set-age (setf a (first args)))
(:greet (format t "Hi, I'm ~A, ~D.~%" n a))))))
(let ((p (make-person "Alice" 30)))
(funcall p :greet) ; Hi, I'm Alice, 30.
(funcall p :set-age 31)
(format t "Now ~A is ~D.~%"
(funcall p :get-name)
(funcall p :get-age))) ; Now Alice is 31.
这种方式:
- 数据封装在闭包中,天然私有
- 消息分发灵活,可随意扩展
- 但缺少 CLOS 的多重分派、class 继承体系
小结
- 想要完整的类/对象系统,就用 CLOS:
defclass
/defgeneric
/defmethod
,支持多重继承、多分派、MOP。
- 如果只是想定义简单记录,用
defstruct
最为轻便。
- 若要最大灵活性(甚至模拟 prototype 式面向对象),可用 闭包+消息 的方法——但手工调度成本较高。
根据项目需求,灵活选用即可。