第 7 课作业
第 7 课面向对象编程作业:包含 person 类扩展、usual、随机数生成器、可乐机、扑克牌堆和礼貌对象六个必做练习,以及一道多重继承挑战题。
在终端中输入以下命令,把模板文件复制到当前目录(注意末尾的句点):
cp ~cs61as/autograder/templates/hw7.scm .
你也可以从这里下载。
练习 1
修改模板中给出的 person 类,添加一个 repeat 方法,它会重复刚才说过的话。例如:
> (define brian (instantiate person 'brian))
brian
> (ask brian 'repeat)
()
> (ask brian 'say '(hello))
(hello)
> (ask brian 'repeat)
(hello)
> (ask brian 'greet)
(hello my name is brian)
> (ask brian 'repeat)
(hello my name is brian)
> (ask brian 'ask '(close the door))
(would you please close the door)
> (ask brian 'repeat)
(would you please close the door)
练习 2
这个练习带你熟悉 usual。
假设我们要定义一个 double-talker 类,用来表示总是把话说两遍的人。例如,看看下面这段对话。
> (define mike (instantiate double-talker 'mike))
mike
> (ask mike 'say '(hello))
(hello hello)
> (ask mike 'say '(the sky is falling))
(the sky is falling the sky is falling)
下面是 double-talker 类的三种候选定义:
(define-class (double-talker name)
(parent (person name))
(method (say stuff) (se (usual 'say stuff) (ask self 'repeat))) )
(define-class (double-talker name)
(parent (person name))
(method (say stuff) (se stuff stuff)) )
(define-class (double-talker name)
(parent (person name))
(method (say stuff) (usual 'say (se stuff stuff))) ).
判断这些定义中哪些能按预期工作。再判断对于哪些消息,这三个版本的响应会有所不同。
练习 3
为一个统计项目,你需要计算大量不同范围内的随机数。(回想一下,(random 10) 返回一个 0 到 9 之间的随机数。)同时,你还需要统计每个范围内计算了多少个随机数。你决定使用面向对象编程。random-generator 类的对象接受两个消息:number 和 count。消息 number 表示「给我一个你范围内的随机数」,而 count 表示「我向你请求过多少个数字?」。这个类有一个实例化参数,用来指定该对象的随机数范围,因此:
(define r10 (instantiate random-generator 10))
会创建一个对象,使得 (ask r10 'number) 返回一个 0 到 9 之间的随机数,而 (ask r10 'count) 返回 r10 已经生成过的随机数的个数。
练习 4
定义类 coke-machine(可乐机)。coke-machine 的实例化参数有两个:机器能容纳的可乐数量,以及一瓶可乐的价格(以美分为单位)。例如:
(define my-machine (instantiate coke-machine 80 70))
创建的机器可容纳 80 瓶可乐,每瓶售价 70 美分。coke-machine 对象必须接受以下消息:
(ask my-machine 'deposit 25)表示存入 25 美分。你可以分多次投入硬币,机器应该记住累计金额。(ask my-machine 'coke)表示按下购买一瓶可乐的按钮。机器随后要么 1) 打印"Not enough money"(钱不够),2) 打印"Machine empty"(机器已空),要么 3) 返回你应得的找零。错误信息应该用display打印(例如(display "Machine empty"))。(交易成功后,机器里不留钱;也就是说,找零不会留在机器里。)(ask my-machine 'fill 60)表示往机器里加 60 瓶可乐。
下面是一个例子:
> (ask my-machine 'fill 60)
> (ask my-machine 'deposit 25)
> (ask my-machine 'coke)
"Not enough money"
> (ask my-machine 'deposit 25) ;; 现在里面共有 50 美分。
> (ask my-machine 'deposit 25) ;; 现在里面共有 75 美分。
> (ask my-machine 'coke)
5 ;; 返回值是 5 美分找零。
你可以假设可乐机有无限的找零供应,且初始时不含任何可乐。
练习 5
我们要用对象来表示一副副扑克牌。给定列表 ordered-deck,其中包含按标准顺序排列的 52 张牌:
(define ordered-deck '(AH 2H 3H ... QH KH AS 2S ... QC KC))
同时给定一个用于打乱列表中元素的函数:
(define (shuffle deck)
(if (null? deck)
'()
(let ((card (nth (random (length deck)) deck)))
(cons card (shuffle (remove card deck))) )))
一个 deck 对象响应两个消息:deal 和 empty?。对于 deal,它返回牌堆顶部的牌,并把那张牌从牌堆中移除;如果牌堆已空,它返回 ()。对于 empty?,它根据牌是否已发完返回 #t 或 #f。请写出 deck 的类定义。实例化时,deck 对象应该包含一副打乱的 52 张牌。
练习 6
我们希望让对象之间更有礼貌。写一个类 miss-manners,它接受一个对象作为实例化参数。新的 miss-manners 对象只接受一个消息,即 please。please 消息的参数依次是:原对象能理解的一个消息,以及该消息的一个参数。(假设发给原对象的所有消息都恰好需要一个额外参数。)
下面是一个使用即将发布的冒险游戏项目中的 person 类的例子:
> (define BH (instantiate person 'Brian BH-office))
BH
> (ask BH 'go 'down)
BRIAN MOVED FROM BH-OFFICE TO SODA
> (define fussy-BH (instantiate miss-manners BH))
> (ask fussy-BH 'go 'east)
ERROR: NO METHOD GO
> (ask fussy-BH 'please 'go 'east)
BRIAN MOVED FROM SODA TO PSL
专家挑战
如果你想要额外的挑战,可以做这些。这些题目不计分。
练习 7
多重继承技术见《面向对象编程——抽象线之上的视角》一文的第 9、10 页。那一节讨论了如何解决含义模糊的继承模式,并特别提到:相比从首选祖父类继承来的方法,选择从次选父类直接继承的方法可能更好。
构造一个这种情况的例子。描述你的例子中的继承层次,列出每个类提供的方法。同时说明在这个例子中,为什么让对象从次选父类(而非首选祖父类)继承某个方法会更合适。
提交作业!
相关说明请见这份指南。其中涵盖基本的终端命令和作业提交方法。
如果在提交过程中遇到任何困难,随时请教助教(TA)!