三种局部状态变量

介绍实例化变量、实例变量和类变量三种局部状态变量的区别与用法,并用 worker 类展示类变量的共享特性。

概述

到目前为止,我们见过的局部状态变量只有实例化变量(instantiation variable)。本小节,我们来看另外两种:实例变量(instance variable)和类变量(class variable)。

实例变量

回顾一下 checking-account 类:

(define-class (checking-account init-balance)
    (parent (account init-balance))
    (method (write-check amount)
        (ask self 'withdraw (+ amount 0.10)) )

每次开支票,账户会被额外收取 10 美分。所有 checking-account 一开始都是 10 美分的费用,但现在我们希望能够随时修改这个费用。一种办法是把 check-fee 加为实例化变量。

(define-class (checking-account init-balance check-fee)
    (parent (account init-balance))
    (method (write-check amount)
        (ask self 'withdraw (+ amount check-fee)) )
    (method (set-fee! fee)
        (set! check-fee fee)) )

(define lily (instantiate checking-account 1000 0.10))
(define ted (instantiate checking-account 1000 0.10))
(define barney (instantiate checking-account 9999 0.10))

但这种写法有点冗余,因为即使我们总是希望费用从 10 美分开始,每次实例化时仍然要手动指定 check-fee。为此,我们引入一个新的子句:instance-vars,正好解决我们的问题。

(define-class (checking-account init-balance)
    (parent (account init-balance))
    (instance-vars (check-fee 0.10))
    (method (write-check amount)
        (ask self 'withdraw (+ amount check-fee)))
    (method (set-fee! fee)
        (set! check-fee fee)) )

实例变量 vs. 实例化变量

实例化变量同时也是实例变量;也就是说,每个实例都拥有自己私有的值。两者的区别仅在于写法和设置初始值的时机:实例化变量在调用 instantiate 时给出值,而其他实例变量的值在类定义中给出。

类变量

第三种局部状态变量是类变量(class variable)。与实例变量不同,一个类变量对整个类只有一个值,类的所有实例共享这个值。举个例子,假设我们有一个 worker 类,所有工人都在做同一个项目。也就是说,只要其中任何一个人工作,完成的工作总量就会增加。另一方面,每个工人在工作时会各自感到饥饿。因此,这个类有一个共同的 work-done 变量,而每个实例有各自独立的 hunger 变量。

(define-class (worker)
    (instance-vars (hunger 0))
    (class-vars (work-done 0))
    (method (work)
        (set! hunger (+ hunger 1))
        (set! work-done (+ work-done 1))
        'whistle-while-you-work ) )
> (define brian (instantiate worker))
brian
> (define matt (instantiate worker))
matt
> (ask matt 'work)
whistle-while-you-work
> (ask matt 'work)
whistle-while-you-work
> (ask matt 'hunger)
2
> (ask matt 'work-done)
2
> (ask brian 'work)
whistle-while-you-work
> (ask brian 'hunger)
1
> (ask brian 'work-done)
3
> (ask worker 'work-done)
3

如你所见,请求任何 worker 对象工作都会使 work-done 变量增加。相比之下,每个工人都有自己的 hunger 实例变量,所以 Brian 工作时,Matt 不会感到饿。你可以向任何实例询问类变量的值,也可以直接询问类本身。这是「消息必须发给实例而非类」这条通常规则的一个例外。

笔记

测试你的理解

我们来设计一个「Dog」类。对于下面每一项,判断它对于狗类来说应该是「child」(子类)、「parent」(父类)、「instance variable」(实例变量)、「class variable」(类变量)还是「method」(方法)。

(例如,如果问题是「cat?」而你认为 cat 应该是 dog 的父类,就输入「parent」。)

对于 Dog 类来说,「name」是:

对于 Dog 类来说,「age」是:

对于 Dog 类来说,「wag-tail」是:

对于 Dog 类来说,「Animal」是:

要点回顾

局部状态变量有三种:实例化变量、实例变量和类变量。

  • 实例化变量(instantiation variable)在用 instantiate 创建对象时指定。
  • 实例变量(instance variable)是每个对象各自拥有、彼此独立的变量;改变其中一个的值不会影响其他变量。
  • 类变量(class variable)是被该类所有对象共享的变量;改变一个类变量的值,该类的每个对象都会察觉到这一变化。