在《实用common lisp编程》的十六和十七章里面,介绍了common lisp的面向对象支持方式,分别是广义函数和CLOS系统。
书本通过文字叙述得非常详细,但是没有附上相应的代码,本着“没有实验过就没有发言权”的求实态度,我决定从带修饰符的辅助方法、带继承的方法、继承和槽、以及多继承等几个主要知识点着手,在代码方面实现一遍,验证书中的内容。
带辅助方法的主方法
第十六章介绍了几个广义函数的修饰符,主要有 :around , :before 和 :after ,而被装饰符修饰的方法称之为“主方法”(primary method),它们的一般运行顺序如下:
1.如果被调用的主方法有 :around 辅助方法,先运行 :around ,在 :around 运行之后,有两种选择,一种是,不调用 call-next-method ,那么整个函数就宣告结束,主方法不会被运行。
另一方面,如果 :around 调用 call-next-method ,这时又出现两种情况:
1) 该主方法还有一个最相关的 :around 辅助方法,那么运行该 :around 函数。(一般是父类的 :around)
2) 该主方法没有最想关的 :around 辅助方法,那么函数的执行权交还给主方法,主方法按正常情况运行,也即是,先运行 :before (如果有的话) ,再运行主方法,最后运行 :after (如果有的话)。
2.如果被调用的主方法(及父类的同名主方法)有 :before 辅助方法,那么它们按照最相关的最先运行的顺序依次执行,其中,不必调用 call-next-method 。
3.如果被调用的主方法(及其父类的同名主方法)有 :after 辅助方法,那么它们按照最相关的最后运行的顺序依次执行,其中,不必调用 call-next-method 。
4.如果主方法没有辅助方法,或者它的辅助方法全部执行完毕,那么执行该主方法。
OK,基本规则就这么多,我们先从 :before 辅助方法开始。
:before 辅助方法,无继承
无继承的 :before 辅助方法将在主方法之前执行,而且, :before 辅助方法执行完毕之后,调用会自动转给主方法,因此,不必调用 call-next-method 。
(defgeneric greet (people))
(defclass person ()
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :before ((people person))
(format t "greet :before ~%"))
以下正是我们想要的执行结果:
(greet (make-instance 'person))
greet :before
greet
NIL
:before 辅助方法,带继承
这次,我们用一个 animal 类作为 person 的父类,调用 greet 方法。
如果一切如常的话,它们的调用顺序应该是 person 类的 greet 的 :before 辅助函数,然后是 animal 类的 greet 的 :before 辅助函数,然后是 person 的 greet 方法。
(defgeneric greet (people))
(defclass animal ()
())
(defmethod greet :before ((people animal))
(format t "animal's greet :before ~%"))
(defclass person (animal)
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :before ((people person))
(format t "greet :before ~%"))
执行结果:
(greet (make-instance 'person))
greet :before
animal's greet :before
greet
NIL
OK,方法如我们所料般执行。
我们可以来讲讲 :after 辅助方法了。
:after 辅助方法,无继承
无继承的带 :after 的辅助函数,将在主方法之后执行。
(defgeneric greet (people))
(defclass person ()
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :after ((people person))
(format t "greet :after ~%"))
执行:
(greet (make-instance 'person))
greet
greet :after
NIL
好极了,下一个。
:after 辅助方法,有继承
这次,我们继续继承 animal 类,并且 animal 类带有一个 greet 方法的 :after 辅助方法。
按书本所说,最不相关的 :after 最先运行,因此,它们的运行顺序应该是: person 类的 greet 方法,animal 类的 greet 方法的 :after 辅助方法, person 类的 greet 方法的 :after 辅助方法。
(defgeneric greet (people))
(defclass animal ()
())
(defmethod greet :after ((people animal))
(format t "animal's greet :after ~%"))
(defclass person (animal)
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :after ((people person))
(format t "greet :after ~%"))
测试:
(greet (make-instance 'person))
greet
animal's greet :after
greet :after
NIL
OK,两个 :after 辅助方法, 第二相关的 animal 类的辅助方法先运行,然后才是第一相关的 person 类本身的辅助方法运行。
接着,来看看 :around 辅助方法。
:around 无继承,不调用 call-next-method
按书上所说,:arond 方法将在主方法以及其他辅助方法之前运行,它的优先级是最高的,而且,只有当 :around 调用 call-next-method 时,方法的执行权才会转回给主方法。
我们这里用一个无继承,不调用 call-next-method 的 ;around 作测试,如果一切正常的话,那么,它只会执行 :around 辅助方法,而不执行主方法或者其他辅助函数。
(defgeneric greet (people))
(defclass person ()
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :around ((people person))
(format t ":around ~%"))
(defmethod greet :before ((people person))
(format t ":before ~%"))
(defmethod greet :after ((people person))
(format t ":after ~%"))
执行试试:
(greet (make-instance 'person))
:around
NIL
WOW,这个 :around 拦截了所有其他方法,只有它自己运行了,真够霸道的,看来用它写一些诸如 memorize 函数的东西应该不错。
:around 无继承,调用 call-next-method
这次,我们在 :around 函数体内加上 (call-next-method) ,这样,函数的执行权就会转交回给被装饰的 greet 函数,那么函数就会以一般的执行顺序执行(:before -> 主方法 -> :after)。
(defgeneric greet (people))
(defclass person ()
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :around ((people person))
(format t ":around ~%")
(call-next-method)) ; new add
(defmethod greet :before ((people person))
(format t ":before ~%"))
(defmethod greet :after ((people person))
(format t ":after ~%"))
测试:
(greet (make-instance 'person))
:around
:before
greet
:after
NIL
嗯,先 :around ,后 :before ,接着 greet 主方法 ,最后 :after ,这就是我们要的。
:around 有继承,父子类都有 :around,都调用 call-next-method
嗯,现在我们有两个类 animal 和 person ,它们都有 :around ,而且两个 :around 都在函数体最后调用 call-next-method 。
按书本所说,它们的执行顺序应该是: person 的greet方法的 :around , animal 的 greet 方法的 :around ,然后是 person 的 greet 方法的 :before ,person 的 greet 主方法,最后是 person 的 greet 方法的 :after 。
(defgeneric greet (people))
(defclass animal ()
())
(defmethod greet :around ((people animal))
(format t "animal's greet :around ~%")
(call-next-method)) ; call person's greet :before
(defclass person (animal)
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :around ((people person))
(format t ":around ~%")
(call-next-method)) ; call animal's greet :around
(defmethod greet :before ((people person))
(format t ":before ~%"))
(defmethod greet :after ((people person))
(format t ":after ~%"))
测试:
(greet (make-instance 'person))
:around
animal's greet :around
:before
greet
:after
嗯,一切正常。
:around ,带继承,父类的 :around 不调用 call-next-method 方法
(defgeneric greet (people))
(defclass animal ()
())
(defmethod greet :around ((people animal))
(format t "animal's greet :around ~%"))
; no (call-next-method) anymore
(defclass person (animal)
())
(defmethod greet ((people person))
(format t "greet ~%"))
(defmethod greet :around ((people person))
(format t ":around ~%")
(call-next-method)) ; call animal's greet :around
(defmethod greet :before ((people person))
(format t ":before ~%"))
(defmethod greet :after ((people person))
(format t ":after ~%"))
还有一点小疑问,就是,如果我们在 animal 类的 greet 方法的 :around 辅助方法里不调用 call-next-method ,会如何?
答案是, animal 类的 greet 方法的 :around 辅助函数就不会将执行权转回给 person 的 greet 主方法,而是就此结束了。
也即是,结果将是:
(greet (make-instance 'person))
:around
animal's greet :around
NIL
终极测试,没有主方法的辅助方法
测试进行到最后,忽然一个狂想从我的脑子里崩出来:一个没有主方法的辅助方法,可能运行吗?
这就来试试:
(defclass person ()
())
(defmethod greet :around ((people person))
(format t ":around ~%"))
测试:
(greet (make-instance 'person))
*** - NO-PRIMARY-METHOD: When calling #<STANDARD-GENERIC-FUNCTION GREET> with
arguments (#<PERSON #x2058E72E>), no primary method is applicable.
The following restarts are available:
RETRY :R1 try calling GREET again
RETURN :R2 specify return values
ABORT :R3 Abort main loop
噢噢,解释器在抱怨我们没有可应用的主方法,因此也证明,没有主方法的辅助方法,是不行滴。
小结
嗯,关于辅助函数的测试就此做完了,下一篇文章,我将测试关于方法的继承的相关问题。
分享到:
相关推荐
Common Lisp中的Common Lisp Object System (CLOS)提供了多重继承和多态性的支持,使得面向对象编程在Common Lisp中成为可能。 #### 5. 跨平台性 Common Lisp编写的程序可以在多种操作系统上运行,包括Windows、...
4. 面向对象编程(CLOS):Common Lisp支持多种编程范式,其中面向对象编程是通过Common Lisp对象系统(Common Lisp Object System,简称CLOS)实现的。CLOS不仅支持类和继承,还支持多重方法分派、组合设计模式等...
本书首先从作者的学习经过及语言历史出发,随后用21个章节讲述了各种基础知识,主要包括:REPL 及Common Lisp 的各种实现、S- 表达式、函数与变量、标准宏与自定义宏、数字与字符以及字符串、集合与向量、列表处理、...
- **面向对象编程**:Common Lisp支持面向对象编程(OOP),尽管它的OOP机制与Java或C++等语言有所不同。Common Lisp的面向对象系统称为CLOS(Common Lisp Object System),它允许程序员创建类和实例,并实现多态性...
这本《Practical Common Lisp》之所以号称Practical,正是因为这本书大量介绍Common Lisp在现实世界中的各种应用方式,算是第一本「入世传教」的Common Lisp著作。《Practical Common Lisp》是目前最畅销的Common ...
《Practical Common Lisp-1st-2005》是一本专注于Common Lisp编程语言的实用书籍,作者Peter Seibel通过这本书向读者展示了如何使用Common Lisp来解决真实世界中的问题,强调程序员作为工程师和艺术家的双重身份,而...
1. **Common Lisp基础**:Common Lisp是一种通用的多范式编程语言,支持过程式、面向对象、函数式和反射等多种编程风格。书中会介绍其基本语法,包括S表达式、读取-求值-打印循环(REPL)以及Lisp代码的读取和评估机制...
- **定义与背景**:Common Lisp是一种功能强大的多范式编程语言,是Lisp家族的一员,具有丰富的历史和深厚的社区支持。作为一种标准化的Lisp方言,它由多个Lisp方言合并而成,旨在为程序员提供一个统一且强大的开发...
该资源包含了 17 章节,从基础的列表、特殊数据结构、控制流程、函数、输入与输出、符号、数字、宏、Common Lisp 对象系统、结构、速度、进阶议题到高级主题的推论、生成 HTML、对象等。 ANSI Common Lisp 是一种...
这是一本不同寻常的Common Lisp 入门书。本书首先从作者的学习经过及语言历史出发,随后用21 个章节讲述了各种基础知识,主要包括:REPL 及Common Lisp 的各种实现、S- 表达式、函数与变量、标 准宏与自定义宏、数字...
- **函数式编程**:Lisp鼓励函数式编程风格,函数是第一类对象,可以作为参数传递,也可以作为返回值。 2. **Common Lisp简介** - **标准统一**:与其他Lisp方言相比,Common Lisp是标准化的,有ANSI Common Lisp...
12. **第11章:Common Lisp对象系统(CLOS)**: - 详细分析了CLOS这一面向对象编程框架的原理和用法。 - 通过具体案例展示了如何利用CLOS来构建复杂的应用程序架构。 13. **第12章:结构(Structure)**: - ...
- **第十一章:Common Lisp 对象系统(CLOS)**:讲述了 Lisp 中的对象系统 CLOS,这是 Lisp 中实现面向对象编程的主要方式。 - **第十二章:结构(Structure)**:讲解了 Lisp 中用于定义固定字段记录的数据类型...
1. **代数运算**:Common Lisp可以通过符号计算来执行复杂的代数运算,如求解多项式方程、简化表达式等。 2. **函数定义**:在Common Lisp中,函数可以接受符号作为参数,并返回新的符号或表达式。 3. **宏定义**:...
common-lisp-the-language-second-edition.PDF