局部状态

通过银行账户的例子讲解 OOP 中如何用 ask 进行消息传递,以及对象状态与局部状态变量的概念。

消息传递

在 OOP 中,让事情发生的方式是「请求」某个对象替你做某事。我们这样做的方式与第 6 课中的「消息传递」类似。用 OOP 的词汇该怎么做呢?

假设我们有两个对象:Matt-Account 和 Brian-Account,它们是 bank-account 类的实例,分别保存 Matt 和 Brian 拥有的金额。(你现在还无法在 Scheme 中输入这些代码!我们假设之前已经创建好了这些对象。)

> (ask Matt-Account 'balance)
1000

> (ask Brian-Account 'balance)
10000

> (ask Matt-Account 'deposit 100)
1100

> (ask Brian-Account 'withdraw 200)
9800

> (ask Matt-Account 'balance)
1100

> (ask Brian-Account 'withdraw 200)
9600

ask

我们用 ask 过程来告诉对象执行某个动作。在上面的例子中,银行账户对象接受 3 种消息:

  • balance
  • deposit
  • withdraw

对每一种消息,银行账户对象都知道需要执行什么操作。注意,有些消息需要附带额外信息:

  • 对于 balance,不需要任何额外参数,它返回该账户中的金额。

    > (ask Matt-Account 'balance)
    1000
  • 对于 deposit 和 withdraw,我们还需要一个参数来指定存入或取出的金额。

    > (ask Matt-Account 'deposit 50000)
    51000

这里的隐喻是:对象「知道如何」完成某些任务。这些任务被称为方法(method)。

笔记

测试你的理解

假设我们为 Max 开了一个银行账户,然后我们输入以下表达式:

(ask max-account 'balance)
1000

(define withdraw 'deposit)

下面的表达式会返回什么?

(ask max-account 'withdraw 100)

如果不是上一个表达式,而是调用下面这个表达式,结果又会如何?

(ask max-account withdraw 100)

状态

看看下面这些调用:

> (ask matt-account 'balance)
500

> (ask brian-account 'balance)
9999

> (ask matt-account 'deposit 500)
1000

> (ask matt-account 'balance)
1000

> (ask matt-account 'withdraw 200)
800

> (ask matt-account 'balance)
800

> (ask brian-account 'balance)
9999

笔记

测试你的理解

我们多次调用 (ask matt-account 'balance),每次得到的值都不一样。这告诉了我们 OOP 的什么特性?

matt-account 和 brian-account 都返回各自账户里有多少钱。Matt 对自己账户的操作(对 matt-account 的方法调用)会影响 Brian 的账户吗?

OOP 范式 vs. 函数式编程范式

在第一个问题中,我们看到 Matt 的余额随着每次存取而变化。这感觉很自然,因为银行账户就是这样运作的。但是,在我们一直使用的函数式编程范式中,我们会预期同一个调用返回相同的值。

在 OOP 范式中,对象拥有状态(state)。也就是说,它们对「过去发生在自己身上的事情」有所记忆。在这个例子中,银行账户有一个余额,当你存入或取出一些钱时,余额就会变化。

局部状态变量

在第二个问题中,我们看到虽然 Matt 有自己的 'balance、Brian 也有自己的 'balance,但它们互不干扰。

用 OOP 的行话说,'balance 是一个局部状态变量(local state variable),也叫实例变量(instance variable)。同一个实例变量在不同实例上会有不同的值。

这里我们可以和第 1 单元中见过的 square 与 cube 的定义做一个类比:

(define (square x)
    (* x x))

和

(define (cube x)
    (* x x x))

两个定义都使用了 x,但它们是相互独立的。