clos 介绍

广义函数

前言:来自C++阵营的人们倾向于认为C++的特定方面,例如严格的数据封装是面向对象的关键特征,有这些观点的人们通常是C++,Eiffel或者Java这类静态语言的拥护者,他们不认为common lisp是真正的面向对象。那些将消息传递视为面向对象关键特征的人们也不会很高兴,因为Common lisp在声称自己是面向对象的同时,其面向广义函数的设计提供了纯消息传递所无法提供的自由度

面向对象的基本思想就是一种组织程序的强大方式(也就是见到什么对象用什么方法):定义数据类型并将操作关联在那些数据类型上,也就是由具体的对象来决定。

所有对象都是某个特定类的实例。

在一个特定对象上调用方法,然后该对象所属的类决定运行什么代码。这种方法调用的模型称为消息传递(message passing)

Defgeneric

除了缺少函数体之外,Defgeneric的基本形式与defun相似,它的形参列表指定了那些定义在该广义函数上的所有方法都必须接受的参数。

对于广义函数的话,你必须首先定义一些class,用于Defmethod进行特化时的需要。

因为Defgeneric是抽象的,不提供实现,最好带上选项:documentation,描述它的用途

CL-USER> (defgeneric withdraw(account amount)
   (:documentation "withdraw the specified amount from the account"))
#<STANDARD-GENERIC-FUNCTION WITHDRAW (0)>
Defmethod

方法的形参必须与广义函数保持一致,更一般情况,方法必须带有广义函数指定的相同数量的必要和可选参数,并且必须可以接受对应于任何&rest或&key形参的参数。

方法提供了广义函数用于特定参数类的实现,

也许广义函数系统和一个消息传递系统之间的区别在于方法并不属于类,而是广义函数,其负责在一个特定调用中检测哪个或哪个方法将被运行

方法通过特化那些由广义函数所定义的必要参数,来表达他们可以处理的参数类型。

Defmethod类似Defun形式,唯一的区别在于必要形参可以接受通过将形参换成两个元素列表进行特化。

第一个是形参名,也就是下面进行方法构造时你用的可以指向等会将要输入实参的。

第二个是特化符,要么是类的名字(可以被子类应用)要么是EQL特化符(指定了方法所对应的特定对象,同一个时才兼容)

  (defmethod withdraw ((account bank-account) amount)(list 2))

当一个广义函数被调用时,它将那些被传递的实际参数与它的每个方法的特化符进行比较找出可应用方法(application),简而言之也就是根据实参找匹配特化符。

下面是一个EQL特化符的例子,,因为*account-of-bank-president*是一个引用。我现在写他的原因就是记住如何访问某一个对象中的特定项,你可以联想到setf中设置某个位置的值时的情况。(balance account) 表示的是一个位置的引用,你可以对他赋值或者查询他。

CL-USER> (defmethod withdraw ((account (eql *account-of-bank-president*)) amount)
        (let ((overdraft (- amount (balance account))))
         (when (plusp overdraft)
           (incf (balance account)(embezzle *bank* overdraft))))
           (call-next-method))

二:方法组合

广义函数相比消息传递完全颠覆了方法的调度过程,使得广义函数而不是类成为了主要推动者。

通过组合可应用的方法来构造有效方法的概念是广义函数概念的核心。并且是广义函数可以支持消息传递系统中没有的机制的关键(消息传递系统中实现不了,但是在广义函数中可以实现的一些机制)。

构造有效方法:

首先,广义函数基于被传递的实际参数构造一个可应用的方法列表。

其次,这个可应用方法的列表按照他们参数特化符中的特化程度排序。

最后,根据排序后列表中的顺序来取出这些方法并将他们的代码组合起来以产生有效方法。

第一步的操作就是,将实际参数与它的每一个方法中的对应参数特化符进行比较。当且仅当所有特化符和对应的参数兼容,一个方法便可用。(注意他说的是所有的特化符,可以理解为实参必须与形参对应,特化符也就是实参)

匹配原则::

当特化符是类的名字时,如果该类的名字是参数的实际类名或他的一个基类的名字,那么特化符将是兼容的。(实参是形参类名的实例或者是他基类的实例,都是可以接受的),不带有显示特化符的形参将隐式特化到类T上从而与任何参数兼容(就像上面我们例子中account就被隐式特化到T上去,按照我的理解是他可以整数对象,浮点数对象,或者其他对象,但是他们基类都是T所以是兼容的,但是有个问题就是现在如果实参是string的话,也同样兼容啊,也就是那个特定方法同样可以被调用呀。)

一个EQL特化符当且仅当参数和特化符 中所指的对象是同一个时才是兼容的。它这个就相于比上面的卡的紧了,如果再是基类的对象传过来的话就是不行。

第二步是排序,因为经过第一个的过程以后,只剩下可用方法在排序,也就是类的名字要么是对应参数的对象要么是它基类的对象,所以说当有两个特化符不同时,也就是说实参对象不同,那么其中一个将是另一个的子类,因为我们这个是显示表达这个位置要么是参数的对象,要么是它的基类的对象。并且很明显子类比基类更相关。

这儿有一个特化性如果实现多重继承。

第三步是标准方法组合,默认情况下,广义函数使用一种称为标准方法组合(standard method combination)

主方法(Primary method),也就是我们提供广义函数的主要实现。

辅助方法(auxiliary method): :before  :after  :around,(有点类似数据库中的触发器机制)他的定义可以用DEFMETHOD像主方法那样书写

CL-USER> (defmethod withdraw :before((account (eql *account-of-bank-president*)) amount)

:before方法:(more spectific first)用来做任何需要确保主方法可以运行的准备工作,最相关者优先的顺序。每一个最相关的将有机会设置环境以便不太相关before或主方法成功运行,它不需要调用call-next-method来将控制传递给其余的方法。

:after 方法:(most specific last)都在主方法之后,以最不相关者优先的顺序进行,每一个更相关的:after方法将有机会再所有主方法和更不想关的:after之后进行清理工作。

:around方法:最相关:around方法的代码将在其他任何代码之前运行,在around方法的主体中,call-next-method将指向下一个最相关的:around方法的代码,或是在最不相关的:around方法中指向由:before方法,主方法和:after方法组成的复合体。所有的:around方法都含有一个对call-next-method的调用,否则除最相关:around方法外,将不会执行下面代码。


下面是ANSI Common lisp中关于generic function跟message passing对比 

   Object-oriented programming is a confusing topic partly because there are two models of how to do it: the message passing model and the generic function model. The message-passing model came first. Generic functions are a generalization of message-passing.

   In the message-passing model, methods belong to objects, and are inherited in the same sense that slots are. To find the area of an object, we send it an  area message,

tell obj area

and this invokes whatever area method obj has or inherits.

   Sometimes we have to pass additional arguments. For example, a move method might take an argument specifying how far to move. If we wanted to tell obj to move 10, we might send it the following message:

tell obj move 10

If we put this another way,

(move obj 10)

the limitation of the message-passing model becomes clearer. In message passing, we only specialize the first parameter. There is no provision for methods involving multiple objects—indeed, the model of objects responding to messages makes this hard even to conceive of.In the message-passing model, methods are of objects, while in the generic function model, they are specialized/or objects. If we only specialize the first parameter, they amount to exactly the same thing. But in the generic function model, we can go further and specialize as many parameters as we need to. This means hat, functionally, the message-passing model is a subset of the generic function model. If you have generic functions, you can simulate message-passing by only specializing the first parameter.


你可能感兴趣的:(clos 介绍)