项目 3:搭档 B 练习
Project 3 中搭档 B 的第一部分练习(B3–B5):take-all、属性表系统、热点与笔记本电脑。
第 B3 题
为人物定义一个方法 take-all。如果收到这条消息,人物应当 take(拿走)当前地点里所有还不属于任何人的物品。
> (ask someperson 'take-all)
第 B4 题:第一部分
现实中谁都能从谁那里随便拿东西,这很不真实。我们想给角色一项 strength,这样只有第一个人比第二个人 strength 高时,第一个人才能从第二个人那里拿走东西。
不过,我们不打算给 person 类添一个局部 strength 变量而把类搞乱。因为可以预见,随着程序进一步开发,我们想添加的属性还会有很多。人物可以有魅力或智慧;物品可以是食物或不是;地点可以在室内或不在室内。因此,你要创建一个叫 basic-object 的类,它持有一个叫 properties 的局部变量,里面是一张属性—值表,就像第 6 课里 get 和 put 用的那种。不过,get 和 put 针对的是所有操作共用的一个固定表;而这里我们需要每个对象各有一张表。文件 tables.scm 里提供了表抽象数据类型的实现:
- 构造函数:
(make-table)返回一个新的空表。 - 赋值函数:
(insert! key value table)向表中添加一组键值对。 - 选择函数:
(lookup key table)返回对应的值,若表中没有该键则返回#f。
表的实现原理见 SICP 3.3.3(第 266–268 页)。目前先把它当作现成的基本工具。
你要修改 person、place 和 thing 类,让它们继承自 basic-object。这个对象会接受一条 put 消息,使下面的调用行为正确:
> (ask Brian 'put 'strength 100)
此外,basic-object 应当把任何其他不认识的消息都当作对同名属性的请求,因此:
> (ask Brian 'strength)
100
应当无需在类定义里写一个显式的 strength 方法就能工作。
别忘了,属性表机制在查询表中没有的属性时会返回 #f。这意味着,即使我们没有往某个对象里 put 过该属性,下面这样的调用也永远不该报错:
> (ask Brian 'charisma)
这对真假型属性很重要:除非我们显式 put 一个 #t 值,它们会自动是 #f(而不是报错)。
给人物一些合理的初始 strength。(每个新实例化的人物对象都应当一样。)以后他们可以通过吃东西变强。
第 B4 题:第二部分
你会注意到,类型判定过程 person? 会检查参数的类型是否是列表 '(person police thief) 的成员。这意味着 person? 过程必须维护一个所有继承自 person 的类的列表——如果我们新建一个子类,这就很烦人。
我们将利用属性表来实现一个更好的类型检查系统。如果我们给 person 类加一个名为 person? 的方法,并让它总是返回 #t,那么任何属于人物类型的对象都会自动继承这个方法。不继承自 person 的对象既找不到 person? 方法,在属性表里也没有 person? 条目,于是会返回 #f。
同理,地点应当有 place? 方法,物品应当有 thing? 方法。
> (ask brian 'person?)
#t
请添加这些类型方法,并把(位于 adv.scm 最底部的)类型判定过程的实现改成这个新版本。别忘了加上 place? 的定义。
新的类型判定过程应当实现如下行为:
> (person? brian)
#t
> (place? soda)
#t
> (thing? coffee)
#t
记住,person? 应当对继承自 person 的类(比如后面定义的 thief 和 police)也有效。place? 和 thing? 同理。
第 B5 题:第一部分
当今时代,很多地方允许你联网。请把 hotspot 定义为一种允许网络连接的地点。每个热点都应当有一个 name 和一个 password 作为实例化变量,连接时必须知道它们。
> (define library (instantiate hotspot 'library 1234))
;热点的名字是 library,密码是 1234
(注意:我们设想的是按网络设密码,不是像你用 AirBears 那样按人设密码。)热点有一个 connect 方法,接受两个参数:一台 laptop(一种 thing,马上要发明)和一个密码。如果密码正确,且笔记本电脑在该热点内,就把它加进已连接笔记本电脑的列表;否则返回一个错误。当笔记本电脑离开热点时,把它从列表中移除。
> (ask library 'connect somelaptop 1234)
热点还有一个 surf 方法,接受两个参数:一台笔记本电脑和一个文本字符串,例如:
"http://www.cs.berkeley.edu"
如果笔记本电脑已联网,那么 surf 方法应当执行:
(system (string-append "lynx " url))
其中 URL 是文本字符串参数(注意 “lynx ” 里 x 后面有个空格)。否则返回一个错误。
> (ask library 'surf somelaptop "http://www.cs.berkeley.edu")
第 B5 题:第二部分
现在来发明 laptop 类。一台笔记本电脑有一个实例化变量,即它的名字。
> (define somelaptop (instantiate laptop 'somelaptop)
笔记本电脑是一种 thing,另有两个方法:connect,接受一个密码作为参数,向笔记本电脑所在的地点发一条 connect 消息。如果密码错误,返回一个错误。
> (ask somelaptop 'connect 1234)
笔记本电脑还有另一个方法 surf,接受一个 URL 文本字符串作为参数,向它所在的地点发一条 surf 消息。因此,每当笔记本电脑进入一个新的热点,用户就必须请求连接该热点的网络;笔记本电脑离开热点时,必须自动断开网络连接。(如果它在一个非热点的地方,surf 消息不会被理解;如果它在热点里但未连接,返回一个错误。)
> (ask somelaptop 'surf "www.berkeley.edu")