Advertisement
Guest User

Untitled

a guest
Dec 18th, 2014
189
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.34 KB | None | 0 0
  1. #lang scribble/manual
  2. @(require "../web/utils.rkt"
  3. (for-label (only-in lang/htdp-intermediate-lambda define-struct ...))
  4. (for-label (except-in class1 define-struct ... length))
  5. (for-label 2htdp/image)
  6. (for-label class1/universe))
  7.  
  8. @(require scribble/eval racket/sandbox)
  9. @(define the-eval
  10. (let ([the-eval (make-base-eval)])
  11. (the-eval '(require (for-syntax racket/base)))
  12. (the-eval '(require class1))
  13. (the-eval '(require 2htdp/image))
  14. (the-eval '(require (prefix-in r: racket)))
  15. the-eval))
  16.  
  17. @title[#:tag "lec04"]{Inheritance and interfaces}
  18.  
  19. @section{Discussion of assignment 2}
  20.  
  21. @subsection{Lists}
  22.  
  23. On the subject of the list finger exercises, we got email like this:
  24.  
  25. @nested[#:style 'inset]{ We are not allowed to use
  26. @racketmodname[class0] lists; are we allowed to use @racket[null] (aka
  27. @racket[empty]), or must we create our own version of that too? }
  28.  
  29. and:
  30.  
  31. @nested[#:style 'inset]{
  32. Should my functions also apply for empty lists (i.e. have empty lists
  33. be represented as objects), or could we simply use the built-in
  34. @racket[empty] datatype (on which none of my methods would work)?}
  35.  
  36. There are two things worth noting, one is important and one is not:
  37. @itemlist[
  38.  
  39. @item{The @racket[empty] value is a list and the assignment
  40. specifically prohibited using lists; so even if you could use
  41. @racket[empty] to represent the empty list, this wouldn't live up to
  42. the specification of the problem.}
  43.  
  44. @item{You won't be able to represent the empty list with a value that
  45. is not an object. The problem asks you to implement @emph{methods}
  46. that can be invoked on lists; the requirement to have methods
  47. dictates that lists are represented as objects since only an object
  48. can understand method sends. If the empty list were represented with
  49. @racket[empty], then @racket[(send empty length)] would blow-up when
  50. it should instead produce @racket[0]. Moreover, if the empty list
  51. were not represented as an object, the design of a @racket[cons%] class will
  52. break too since the template for a method in the @racket[cons%] class
  53. is something along the lines of:
  54. @racketblock[
  55. (define-class cons%
  56. (fields first rest)
  57. (define/public (method-template ...)
  58. (field first) ...
  59. (send (field rest) method-template ...)))
  60. ]
  61.  
  62. Notice that the method is sent to the rest of this list. If the rest
  63. of the list is empty, then this will break unless the empty list is an
  64. object, and moreover, an object that understands @racket[method-template].}
  65.  
  66. #:style 'ordered]
  67.  
  68. @subsection{Zombie}
  69.  
  70. On the subject of the zombie game, we got a bunch of emails like this:
  71.  
  72. @nested[#:style 'inset]{ My partner and I have come upon a dilemma in
  73. Homework 2 in Honors Fundies 2 because we have not learned inheritance
  74. yet. We can have two separate classes Player and Zombie and tie them
  75. together in a union called Being, but that'll result in some
  76. significant copy-and-paste code. The alternative is to make one class
  77. called Being and in the *single* function that differs between players
  78. and zombies, use a @racket[cond] based on a field (@racket['zombie] or
  79. @racket['player]). We were told not to do this, but we were also told
  80. copy-and-pasting code is bad...hence the dilemma.}
  81.  
  82. and:
  83.  
  84. @nested[#:style 'inset]{ My partner and I have been working through
  85. the current assignment and with our representation of zombies and
  86. players there is a lot of reused code. I was wondering if there is
  87. some form of inheritance in the @racketmodname[class0] language that
  88. has not been covered. I think that inheritance would make our current
  89. code much cleaner by solving the problem of reused code. Is there a
  90. way to implement inheritance in the @racketmodname[class0] language
  91. currently?}
  92.  
  93.  
  94. One of the subjects of today's lecture is inheritance, however let's
  95. step back and ask ourselves if this dilemma is really as inescapable
  96. as described.
  97.  
  98. First, let's consider the information that needs to be represented in
  99. a game. When you look at the game, you see several things: live
  100. zombies, dead zombies, a player, and a mouse. That might lead you to
  101. a representation of each of these things as a separate class, in which
  102. case you may later find many of the methods in these classes are
  103. largely identical. You would like to abstract to avoid the code
  104. duplication, but thus far, we haven't seen any class-based abstraction
  105. mechanisms. So there are at least two solutions to this problem:
  106.  
  107. @itemlist[
  108. @item{Re-consider your data definitions.
  109.  
  110. Program design is an iterative process; you are continually revising
  111. your data definitions, which induces program changes, which may cause
  112. you to redesign your data definitions, and so on. So when you find
  113. yourself wanting to copy and paste lots of code, you might want to
  114. reconsider how you're representing information in your program. In
  115. the case of zombie, you might step back and see that although the
  116. game consists of a player, dead zombies, live zombies, and a mouse,
  117. these things have much in common. What @emph{changes over time}
  118. about each of them is their position. Otherwise, what makes them
  119. different is how they are rendered visually. But it's important to
  120. note that way any of these things are rendered @emph{does not change
  121. over the course of the game}---a dead zombie is @emph{always} drawn
  122. as a gray dot; a live zombie is @emph{always} drawn as a green dot;
  123. etc. Taking this view, we can represent the position of each entity
  124. uniformly using a single class. This avoids duplicating method
  125. definitions since there is only a single class to represent each of
  126. these entities.
  127. }
  128. @item{Abstract using the functional abstraction recipe of last semester.
  129.  
  130. Just because we are in a new semester and studying a new paradigm of
  131. programming, we should not throw out the lessons and techniques we've
  132. previously learned. In particular, since we are writing programs in
  133. a multi-pararadigm language---one that accomodates both structural
  134. and functional programming @emph{and} object-oriented
  135. programming---we can mix and match as our designs necessitate. In
  136. this case, we can apply the recipe for @emph{functional abstraction}
  137. to the design of identical or similar methods, i.e. two methods with
  138. similar implementations can be abstracted to a single point of
  139. control by writing a helper function that encapsulates the common
  140. code. The method can then call the helper function, supplying as
  141. arguments values that encapsulate the differences of the original
  142. methods. } #:style 'ordered ]
  143.  
  144. That said, one of the subjects of today's lecture will be an
  145. object-oriented abstraction mechanism that provides a third
  146. alternative to resolving this issue.
  147.  
  148. @section{Inheritance}
  149.  
  150. @subsection{Method inheritance with binary trees}
  151.  
  152. Last time, we developed classes for representing binary trees and
  153. wrote a couple methods for binary trees. The code we ended with was:
  154.  
  155. @#reader scribble/comment-reader
  156. (racketmod
  157. class0
  158. ;; A BT is one of:
  159. ;; - (new leaf% Number)
  160. ;; - (new node% Number BT BT)
  161.  
  162. (define-class leaf%
  163. (fields number)
  164. ;; -> Number
  165. ;; count the number of numbers in this leaf
  166. (define/public (count)
  167. 1)
  168. ;; Number -> BT
  169. ;; double the leaf and put the number on top
  170. (define/public (double n)
  171. (new node% n this this)))
  172.  
  173. (define-class node%
  174. (fields number left right)
  175. ;; -> Number
  176. ;; count the number of numbers in this node
  177. (define/public (count)
  178. (+ 1
  179. (send (field left) count)
  180. (send (field right) count)))
  181. ;; Number -> BT
  182. ;; double the node and put the number on top
  183. (define/public (double n)
  184. (new node% n this this)))
  185.  
  186. (define ex1 (new leaf% 7))
  187. (define ex2 (new node% 6
  188. (new leaf% 8)
  189. (new node% 4
  190. (new leaf% 3)
  191. (new leaf% 2))))
  192. (define ex3 (new node% 8
  193. (new leaf% 2)
  194. (new leaf% 1)))
  195.  
  196. (check-expect (send ex1 count) 1)
  197. (check-expect (send ex2 count) 5)
  198. (check-expect (send ex3 count) 3)
  199.  
  200. (check-expect (send ex1 double 5)
  201. (new node% 5 ex1 ex1))
  202. (check-expect (send ex3 double 0)
  203. (new node% 0 ex3 ex3))
  204. )
  205.  
  206. One of the troublesome aspects of this code is the fact that the two
  207. implementations of @racket[double] are identical. If we think by
  208. analogy to the structural version of this code, we have something like
  209. this:
  210.  
  211. @#reader scribble/comment-reader
  212. (racketmod
  213. class0
  214. ;; A BT is one of:
  215. ;; - (make-leaf Number)
  216. ;; - (make-node Number BT BT)
  217. (define-struct leaf (number))
  218. (define-struct node (number left right))
  219.  
  220. ;; BT Number -> BT
  221. ;; Double the given tree and put the number on top.
  222. (define (double bt n)
  223. (cond [(leaf? bt) (make-node n bt bt)]
  224. [(node? bt) (make-node n bt bt)]))
  225. )
  226.  
  227. We would arrive at this code by developing the @racket[double]
  228. function according to the design recipe; in particular, this code
  229. properly instantiates the template for binary trees. However, after
  230. noticing the duplicated code, it is straightforward to rewrite this
  231. structure-oriented function into an equivalent one that duplicates no
  232. code. All cases of the @racket[cond] clause produce the same result,
  233. hence the @racket[cond] can be eliminated, replaced by a single
  234. occurrence of the duplicated answer expressions:
  235.  
  236. @#reader scribble/comment-reader
  237. (racketblock
  238. ;; BT Number -> BT
  239. ;; Double the given tree and put the number on top.
  240. (define (double bt n)
  241. (make-node n bt bt))
  242. )
  243.  
  244. But switching back to the object-oriented version of this code, it is
  245. not so simple to "eliminate the @racket[cond]"---there is no
  246. @racket[cond]! The solution, in this context, is to abstract the
  247. identical method defintions to a common @emph{super} class. That is,
  248. we define a third class that contains the method shared among
  249. @racket[leaf%] and @racket[node%]:
  250.  
  251. @#reader scribble/comment-reader
  252. (racketblock
  253. (define-class bt%
  254. ;; -> BT
  255. ;; Double this tree and put the number on top.
  256. (define/public (double n)
  257. (new node% n this this)))
  258. )
  259.  
  260. The @racket[double] method can be removed from the @racket[leaf%] and
  261. @racket[node%] classes and instead these class can rely on the
  262. @racket[bt%] definition of @racket[double], but to do this we must
  263. establish a relationship between @racket[leaf%], @racket[node%] and
  264. @racket[bt%]: we declare that @racket[leaf%] and @racket[node%] are
  265. @emph{subclasses} of @racket[bt%], and therefore they @emph{inherit}
  266. the @racket[double] method; it is as if the code were duplicated
  267. without actually writing it twice:
  268.  
  269. @#reader scribble/comment-reader
  270. (racketblock
  271. (define-class leaf%
  272. (super bt%)
  273. (fields number)
  274. ;; -> Number
  275. ;; count the number of numbers in this leaf
  276. (define/public (count)
  277. 1))
  278.  
  279. (define-class node%
  280. (super bt%)
  281. (fields number left right)
  282. ;; -> Number
  283. ;; count the number of numbers in this node
  284. (define/public (count)
  285. (+ 1
  286. (send (field left) count)
  287. (send (field right) count))))
  288. )
  289.  
  290. To accomodate this new feature---@emph{inheritance}---we need to
  291. adjust our programming language. We'll now program in
  292. @racketmodname[class1], which is a superset of
  293. @racketmodname[class0]---all @racketmodname[class0] programs are
  294. @racketmodname[class1] programs, but not vice versa. The key
  295. difference is the addition of the @racket[(super _class-name)] form.
  296.  
  297. @(the-eval
  298. '(module m class1
  299. (provide bt% node% leaf%)
  300. (define-class bt%
  301. ;; -> BT
  302. ;; Double this tree and put the number on top.
  303. (define/public (double n)
  304. (new node% n this this)))
  305.  
  306. (define-class node%
  307. (super bt%)
  308. (fields number left right)
  309. ;; -> Number
  310. ;; count the number of numbers in this node
  311. (define/public (count)
  312. (+ 1
  313. (send (field left) count)
  314. (send (field right) count))))
  315.  
  316. (define-class leaf%
  317. (super bt%)
  318. (fields number)
  319. ;; -> Number
  320. ;; count the number of numbers in this leaf
  321. (define/public (count)
  322. 1))))
  323.  
  324. @(the-eval '(require 'm))
  325.  
  326. At this point we can construct binary trees just as before,
  327. and all binary trees understand the @racket[double] method
  328. even though it is only defined in @racket[bt%]:
  329.  
  330. @interaction[#:eval the-eval
  331. (new leaf% 7)
  332. (send (new leaf% 7) double 8)
  333. (send (send (new leaf% 7) double 8) double 9)
  334. ]
  335.  
  336. @subsection{"Abstract" classes}
  337.  
  338. At this point, it is worth considering the question: what does a
  339. @racket[bt%] value represent? We have arrived at the @racket[bt%]
  340. class as a means of abstracting identical methods in @racket[leaf%]
  341. and @racket[node%], but if I say @racket[(new bt%)], as I surely can,
  342. what does that @emph{mean}? The answer is: @emph{nothing}.
  343.  
  344. Going back to our data definition for @tt{BT}s, it's clear that the
  345. value @racket[(new bt%)] is @bold{not} a @tt{BT} since a @tt{BT} is
  346. either a @racket[(new leaf% Number)] or a @racket[(new node% Number BT
  347. BT)]. In other words, a @racket[(new bt%)] makes no more sense as a
  348. representation of a binary tree than does @racket[(new node% "Hello Fred"
  349. 'y-is-not-a-number add1)]. With that in mind, it doesn't make
  350. sense for our program to ever construct @racket[bt%] objects---they
  351. exist purely as an abstraction mechanism. Some languages, such as
  352. Java, allow you to enforce this property by declaring a
  353. class as "abstract"; a class that is declared abstract cannot be
  354. constructed. Our language will not enforce this property, much as it
  355. does not enforce contracts. Again we rely on data definitions to
  356. make sense of data, and @racket[(new bt%)] doesn't make sense.
  357.  
  358. @subsection{Data inheritance with binary trees}
  359.  
  360. Inheritance allows us to share methods amongst classes, but it also
  361. possible to share data. Just as we observed that @racket[double] was
  362. the same in both @racket[leaf%] and @racket[node%], we can also
  363. observe that there are data similarities between @racket[leaf%] and
  364. @racket[node%]. In particular, both @racket[leaf%] and @racket[node%]
  365. contain a @racket[number] field. This field can be abstracted just
  366. like @racket[double] was---we can lift the field to the @racket[bt%]
  367. super class and elimate the duplicated field in the subclasses:
  368.  
  369. @#reader scribble/comment-reader
  370. (racketblock
  371. (define-class bt%
  372. (fields number)
  373. ;; -> BT
  374. ;; Double this tree and put the number on top.
  375. (define/public (double n)
  376. (new node% n this this)))
  377.  
  378. (define-class leaf%
  379. (super bt%)
  380. ;; -> Number
  381. ;; count the number of numbers in this leaf
  382. (define/public (count)
  383. 1))
  384.  
  385. (define-class node%
  386. (super bt%)
  387. (fields left right)
  388. ;; -> Number
  389. ;; count the number of numbers in this node
  390. (define/public (count)
  391. (+ 1
  392. (send (field left) count)
  393. (send (field right) count)))))
  394.  
  395.  
  396. The @racket[leaf%] and @racket[node%] class now inherit both the
  397. @racket[number] field and the @racket[double] method from
  398. @racket[bt%]. This has a consequence for the class constructors.
  399. Previously it was straightforward to construct an object: you write
  400. down @racket[new], the class name, and as many expressions as there
  401. are fields in the class. But now that a class may inherit fields, you
  402. must write down as many expressions as there are fields in the class
  403. definition itself and in all of the super classes. What's
  404. more, the order of arguments is important. The fields defined in the
  405. class come first, followed by the fields in the immediate super class,
  406. followed by the super class's super classes, and so on. Hence, we
  407. still construct @racket[leaf%]s as before, but the arguments to the
  408. @racket[node%] constructor are changed: it takes the left subtree, the
  409. right subtree, and @emph{then} the number at that node:
  410.  
  411. @(the-eval
  412. '(module n class1
  413. (provide bt% node% leaf%)
  414. (define-class bt%
  415. (fields number)
  416. ;; -> BT
  417. ;; Double this tree and put the number on top.
  418. (define/public (double n)
  419. (new node% n this this)))
  420.  
  421. (define-class node%
  422. (super bt%)
  423. (fields left right)
  424. ;; -> Number
  425. ;; count the number of numbers in this node
  426. (define/public (count)
  427. (+ 1
  428. (send (field left) count)
  429. (send (field right) count))))
  430.  
  431. (define-class leaf%
  432. (super bt%)
  433. ;; -> Number
  434. ;; count the number of numbers in this leaf
  435. (define/public (count)
  436. 1))))
  437.  
  438. @(the-eval '(require 'n))
  439.  
  440. @#reader scribble/comment-reader
  441. (racketblock
  442. ;; A BT is one of:
  443. ;; - (new leaf% Number)
  444. ;; - (new node% BT BT Number)
  445. )
  446.  
  447. @interaction[#:eval the-eval
  448. (new leaf% 7)
  449. (new node% (new leaf% 7) (new leaf% 13) 8)
  450. ]
  451.  
  452. Although none of our method so far have needed to access the
  453. @racket[number] field, it is possible to access @racket[number] in
  454. @racket[leaf%] and @racket[node%] (and @racket[bt%]) methods
  455. @emph{as if} they had their own @racket[number] field. Let's write a
  456. @racket[sum] method to make it clear:
  457.  
  458. @margin-note{Notice that the @racket[double] method swapped the order
  459. of arguments when constructing a new @racket[node%] to reflect the
  460. fact that the node constructor now takes values for its fields first,
  461. then values for its inherited fields.}
  462.  
  463. @#reader scribble/comment-reader
  464. (racketblock
  465. (define-class bt%
  466. (fields number)
  467. ;; -> BT
  468. ;; Double this tree and put the number on top.
  469. (define/public (double n)
  470. (new node% this this n)))
  471.  
  472. (define-class leaf%
  473. (super bt%)
  474. ;; -> Number
  475. ;; count the number of numbers in this leaf
  476. (define/public (count)
  477. 1)
  478.  
  479. ;; -> Number
  480. ;; sum all the numbers in this leaf
  481. (define/public (sum)
  482. (field number)))
  483.  
  484. (define-class node%
  485. (super bt%)
  486. (fields left right)
  487. ;; -> Number
  488. ;; count the number of numbers in this node
  489. (define/public (count)
  490. (+ 1
  491. (send (field left) count)
  492. (send (field right) count)))
  493.  
  494. ;; -> Number
  495. ;; sum all the numbers in this node
  496. (define/public (sum)
  497. (+ (field number)
  498. (send (field left) sum)
  499. (send (field right) sum))))
  500. )
  501.  
  502. As you can see, both of the @racket[sum] methods refer to the
  503. @racket[number] field, which is inherited from @racket[bt%].
  504.  
  505. @subsection{Inheritance with shapes}
  506.  
  507. Let's consider another example and see how data and method inheritance
  508. manifests. This example will raise some interesting issues for how
  509. super classes can invoke the methods of its subclasses. Suppose we
  510. are writing a program that deals with shapes that have position. To
  511. keep the example succint, we'll consider two kinds of shapes: circles
  512. and rectangles. This leads us to a union data definition (and class
  513. definitions) of the following form:
  514.  
  515. @margin-note{We are using @tt{+Real} to be really precise about the
  516. kinds of numbers that are allowable to make for a sensible notion of a
  517. shape. A circle with radius @racket[-5] or @racket[3+2i] doesn't make
  518. a whole lot of sense.}
  519.  
  520. @#reader scribble/comment-reader
  521. (racketblock
  522. ;; A Shape is one of:
  523. ;; - (new circ% +Real Real Real)
  524. ;; - (new rect% +Real +Real Real Real)
  525. ;; A +Real is a positive, real number.
  526. (define-class circ%
  527. (fields radius x y))
  528. (define-class rect%
  529. (fields width height x y))
  530. )
  531.  
  532. Already we can see an opportunity for data abstraction since
  533. @racket[circ%]s and @racket[rect%]s both have @racket[x] and
  534. @racket[y] fields. Let's define a super class and inherit these
  535. fields:
  536.  
  537. @#reader scribble/comment-reader
  538. (racketblock
  539. ;; A Shape is one of:
  540. ;; - (new circ% +Real Real Real)
  541. ;; - (new rect% +Real +Real Real Real)
  542. ;; A +Real is a positive, real number.
  543. (define-class shape%
  544. (fields x y))
  545. (define-class circ%
  546. (super shape%)
  547. (fields radius))
  548. (define-class rect%
  549. (super shape%)
  550. (fields width height))
  551. )
  552.  
  553. Now let's add a couple methods: @racket[area] will compute the area of the
  554. shape, and @racket[draw-on] will take a scene and draw the shape on
  555. the scene at the appropriate position:
  556.  
  557. @#reader scribble/comment-reader
  558. (racketblock
  559. (define-class circ%
  560. (super shape%)
  561. (fields radius)
  562.  
  563. ;; -> +Real
  564. (define/public (area)
  565. (* pi (sqr (field radius))))
  566.  
  567. ;; Scene -> Scene
  568. ;; Draw this circle on the scene.
  569. (define/public (draw-on scn)
  570. (place-image (circle (field radius) "solid" "black")
  571. (field x)
  572. (field y)
  573. scn)))
  574.  
  575. (define-class rect%
  576. (super shape%)
  577. (fields width height)
  578.  
  579. ;; -> +Real
  580. ;; Compute the area of this rectangle.
  581. (define/public (area)
  582. (* (field width)
  583. (field height)))
  584.  
  585. ;; Scene -> Scene
  586. ;; Draw this rectangle on the scene.
  587. (define/public (draw-on scn)
  588. (place-image (rectangle (field width) (field height) "solid" "black")
  589. (field x)
  590. (field y)
  591. scn)))
  592. )
  593.  
  594. @(the-eval
  595. '(module p class1
  596. (provide circ% rect%)
  597. (require 2htdp/image)
  598.  
  599. (define-class shape%
  600. (fields x y)
  601.  
  602. ;; Scene -> Scene
  603. ;; Draw this shape on the scene.
  604. (define/public (draw-on scn)
  605. (place-image (send this img)
  606. (field x)
  607. (field y)
  608. scn)))
  609.  
  610. (define-class circ%
  611. (super shape%)
  612. (fields radius)
  613.  
  614. ;; -> +Real
  615. (define/public (area)
  616. (* pi (sqr (field radius))))
  617.  
  618. ;; -> Image
  619. ;; Render this circle as an image.
  620. (define/public (img)
  621. (circle (field radius) "solid" "black")))
  622.  
  623. (define-class rect%
  624. (super shape%)
  625. (fields width height)
  626.  
  627. ;; -> +Real
  628. ;; Compute the area of this rectangle.
  629. (define/public (area)
  630. (* (field width)
  631. (field height)))
  632.  
  633. ;; -> Image
  634. ;; Render this rectangle as an image.
  635. (define/public (img)
  636. (rectangle (field width) (field height) "solid" "black")))))
  637.  
  638. @(the-eval '(require 'p))
  639.  
  640. @examples[#:eval the-eval
  641. (send (new circ% 30 75 75) area)
  642. (send (new circ% 30 75 75) draw-on (empty-scene 150 150))
  643. (send (new rect% 30 50 75 75) area)
  644. (send (new rect% 30 50 75 75) draw-on (empty-scene 150 150))
  645. ]
  646.  
  647. The @racket[area] method is truly different in both variants of the
  648. shape union, so we shouldn't attempt to abstract it by moving it to
  649. the super class. However, the two definitions of the @racket[draw-on]
  650. method are largely the same. If they were @emph{identical}, it would
  651. be easy to abstract the method, but until the two methods are
  652. identical, we cannot lift the definition to the super class. One way
  653. forward is to rewrite the methods by pulling out the parts that differ
  654. and making them seperate methods. What differs between these two
  655. methods is the expression constructing the image of the shape, which
  656. suggests defining a new method @racket[img] that constructs the image.
  657. The @racket[draw-on] method can now call @racket[img] and rewriting it
  658. this way makes both @racket[draw-on] methods identical; the method can
  659. now be lifted to the super class:
  660.  
  661. @#reader scribble/comment-reader
  662. (racketblock
  663. (define-class shape%
  664. (fields x y)
  665.  
  666. ;; Scene -> Scene
  667. ;; Draw this shape on the scene.
  668. (define/public (draw-on scn)
  669. (place-image (img)
  670. (field x)
  671. (field y)
  672. scn)))
  673. )
  674.  
  675. But there is a problem with this code. While this code makes sense
  676. when it occurrs inside of @racket[rect%] and @racket[circ%], it
  677. doesn't make sense inside of @racket[shape%]. In particular, what
  678. does @racket[img] mean here? The @racket[img] method is a method of
  679. @racket[rect%] and @racket[circ%], but not of @racket[shape%],
  680. and therefore the name @racket[img] is unbound in this context.
  681.  
  682. On the other hand, observe that all shapes are either @racket[rect%]s
  683. or @racket[circ%]s. We therefore @emph{know} that the object invoking
  684. the @racket[draw-on] method understands the @racket[img] message,
  685. since both @racket[rect%] and @racket[circ%] implement the
  686. @racket[img] method. Therefore we can use @racket[send] to invoke the
  687. @racket[img] method on @racket[this] object and thanks to our data
  688. definitions for shapes, it's guaranteed to succeed. (The message send
  689. would fail if @racket[this] referred to a @racket[shape%], but remember
  690. that @racket[shape%]s don't make sense as objects in their own right
  691. and should never be constructed).
  692.  
  693. We arrive at the folllowing final code:
  694.  
  695. @#reader scribble/comment-reader
  696. (racketmod
  697. class1
  698. (require 2htdp/image)
  699.  
  700. ;; A Shape is one of:
  701. ;; - (new circ% +Real Real Real)
  702. ;; - (new rect% +Real +Real Real Real)
  703. ;; A +Real is a positive, real number.
  704. (define-class shape%
  705. (fields x y)
  706.  
  707. ;; Scene -> Scene
  708. ;; Draw this shape on the scene.
  709. (define/public (draw-on scn)
  710. (place-image (send this img)
  711. (field x)
  712. (field y)
  713. scn)))
  714.  
  715. (define-class circ%
  716. (super shape%)
  717. (fields radius)
  718.  
  719. ;; -> +Real
  720. ;; Compute the area of this circle.
  721. (define/public (area)
  722. (* pi (sqr (field radius))))
  723.  
  724. ;; -> Image
  725. ;; Render this circle as an image.
  726. (define/public (img)
  727. (circle (field radius) "solid" "black")))
  728.  
  729. (define-class rect%
  730. (super shape%)
  731. (fields width height)
  732.  
  733. ;; -> +Real
  734. ;; Compute the area of this rectangle.
  735. (define/public (area)
  736. (* (field width)
  737. (field height)))
  738.  
  739. ;; -> Image
  740. ;; Render this rectangle as an image.
  741. (define/public (img)
  742. (rectangle (field width) (field height) "solid" "black")))
  743.  
  744. (check-expect (send (new rect% 10 20 0 0) area)
  745. 200)
  746. (check-within (send (new circ% 10 0 0) area)
  747. (* pi 100)
  748. 0.0001)
  749. (check-expect (send (new rect% 5 10 10 20) draw-on
  750. (empty-scene 40 40))
  751. (place-image (rectangle 5 10 "solid" "black")
  752. 10 20
  753. (empty-scene 40 40)))
  754. (check-expect (send (new circ% 4 10 20) draw-on
  755. (empty-scene 40 40))
  756. (place-image (circle 4 "solid" "black")
  757. 10 20
  758. (empty-scene 40 40)))
  759. )
  760.  
  761. @section{Interfaces}
  762.  
  763. [This section is still under construction and will be posted soon. Please
  764. check back later.]
  765.  
  766. @subsection{Lights, redux}
  767.  
  768. @itemlist[
  769. @item{a look back at light}
  770. @item{add a draw method}
  771. @item{make it a world program that cycles through the lights}
  772. @item{what does it mean to be a light? next and draw}
  773. @item{alternative design of light class}
  774. @item{Q: what changes in world? A: nothing.}
  775. @item{add interface to light design. both alternatives implement it,
  776. and the world will work with anything that implements it}]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement