初始化与默认值

讲解 initialize 子句如何在对象创建后立即执行动作,以及 default-method 子句如何处理对象无法理解的消息。

初始化

先浏览一下 penguin 类:

(define-class (penguin name)
    (class-vars (all-penguin nil)
                (favorite-food 'tuna))
    (instance-vars (hunger 50)
                   (weight 350))
    (method (eat)
        (set! hunger (- hunger 1))
        (set! weight (+ weight 5))
        (se 'That favorite-food '(was delicious!))))
> (define jack (instantiate penguin 'jack))

> (ask jack 'eat)
(that tuna was delicious!)

> (ask jack 'hunger)
49

> (ask jack 'weight)
355

一只企鹅有 2 个实例变量:hunger(饥饿度)和 weight(体重)。企鹅类有 2 个类变量:最喜欢的食物 tuna(金枪鱼),以及 all-penguin——一个记录所有已创建企鹅名字的列表。目前,all-penguin 从未被更新。在某些情况下,就像这里,我们希望对象在创建时就做某些事情。我们可以用 initialize 子句来做到这一点。

可爱的企鹅

实例化之后

企鹅对象一被实例化,我们就希望它:

  1. 说出自己的名字,并且
  2. 把自己加入 all-penguin 列表。

下面是用 initialize 子句实现的方式:

(define-class (penguin name)
    (class-vars (all-penguin nil)
                (favorite-food tuna))
    (instance-vars (hunger 50)
                   (weight 350))
    (initialize (print (se '(hi my name is) name))
                (set! all-penguin (cons name all-penguin)))
    (method (eat)
        (set! hunger (- hunger 1))
        (set! weight (+ weight 5))
        (se 'That favorite-food '(was delicious!))))
> (define jack (instantiate penguin 'jack))
(hi my name is jack)

> (define jennie (instantiate penguin 'jennie))
(hi my name is jennie)

> (ask penguin 'all-penguin)
(jennie jack)

> (ask jack 'all-penguin)
(jennie jack)

默认方法

这是我们目前对企鹅类的定义:

(define-class (penguin name)
    (class-vars (all-penguin nil)
                (favorite-food 'tuna))
    (instance-vars (hunger 50)
                   (weight 350))
    (initialize (print (se '(hi my name is) name))
                (set! all-penguin (cons name all-penguin)))
    (method (eat)
        (set! hunger (- hunger 1))
        (set! weight (+ weight 5))
        (se 'That favorite-food '(was delicious!))))

假设我们调用下面的方法:

> (define jack (instantiate penguin 'jack))
(hi my name is jack)

> (ask jack 'favorite-food)
tuna

> (ask jack 'eat)
(That tuna was delicious!)

> (ask jack 'back-flip)
*** Error: No method back-flip in class penguin

看起来 jack 不知道怎么 back-flip(后空翻)。我们的企鹅目前只懂有限的消息,但作为企鹅类的设计者,我们不希望它们对其他消息都抛错误。相反,如果一只企鹅收到不理解的消息,我们希望它转而去吃东西。我们可以用 default-method(默认方法)子句来做到这一点。看看我们对企鹅类新增的部分:

(define-class (penguin name)
    (class-vars (all-penguin nil)
                (favorite-food 'tuna))
    (instance-vars (hunger 50)
                   (weight 350))
    (initialize (print (se '(hi my name is) name))
                (set! all-penguin (cons name all-penguin)))
    (method (eat)
        (set! hunger (- hunger 1))
        (set! weight (+ weight 5))
        (se 'That favorite-food '(was delicious!)))
    (default-method
        (print (se '(I dont know how to) message '(I will eat instead)))
        (ask self 'eat)))

现在我们调用这些方法:

> (define jack (instantiate penguin 'jack))
(hi my name is jack)

> (ask jack 'back-flip)
(I dont know how to back-flip I will eat instead)
(that tuna was delicious!)

> (ask jack 'weight)
355

> (ask jack 'fly)
(I don't know how to fly I will eat instead)
(that tuna was delicious!)

> (ask jack 'weight)
360
后空翻的企鹅

message 与 args

注意,在上面的 default-method 中,我们用 message 得知传给对象的是什么消息。类似地,我们还可以用 args 得知其他参数,它们以一个列表的形式给出。

例如,如果我们调用

(ask jack 'do-math 1 2 5 10)

那么变量 message 会指向 'do-math,而变量 args 会指向 (1 2 5 10)。