Recitation 14



 

Object-oriented Programming, Part 2

Last time we saw:
Encapsulation of state
Method selection implemented with symbols
Handling arbitrary argument lists by having (object message) return a procedure -- the method


Today we talked about:

Asking yourself to do something -- why self is needed
Inheritance -- passing the buck
Explicitly delegating -- very subtle
We used examples from Problem Set 6.


Classes, Is-a and Has-a


Examples of Classes:

Named-Object
Mobile-Thing
Thing
Container
Place
Person
Examples of is-a:
Thing and Place have names, so each is-a Named-Object
Place and Person can contain things, so each is-a Container
Person is-a Mobile-Thing
Examples of has-a:
Thing has-a location that is a Place
Container has-a list of Things


We drew a diagram to show these relationships.


Methods

Each object has local state, but this isn't accessible from outside. Instead, you call "methods". A method is just a procedure that accesses this local state. It can change the state and/or return a value.

Examples of methods:

LOCATION
CHANGE-LOCATION
HAVE-THING?
ADD-THING?
NAME

Which classes do they belong to?


Implementing is-a and has-a

If each object of class A has-a B, then just put a reference to a B in the state of the A object.
If each object of class A is-a B, also store the reference to the B inside the A.

But subtle difference in how method calls are handled.

Objects can call methods on themselves. For example, if you call the SAY method on an object with name George, it might generate the string "George says ...". How will it find its name? It could use the name directly, but more elegant to use the method NAME that's already there. To do this, inside SAY there's a call to NAME. In an OO language like Java, it's implicit that the NAME is called on the same object as the SAY; we'll make it explicit.

Objects can call methods on objects they're connected to. For example, Container has a method ADD-THING, and Thing has a method INSTALL that causes the object to placed into its location. So when you call INSTALL on a Thing, it calls ADD-THING on its Place.

The main reason for inheritance is code sharing. There's no reason to implement the NAME method for Person, since Named-Object already provides it. So if you ask a Person to execute the method NAME, it will delegate it to its Named-Object.

Sometimes a class and its superclass (one it inherits from) will both have version sof a method. When you ask an object of the subclass to do the method, it will be the subclass method that gets executed. This is called overriding. For example, we might have a SAY method for a Thing that just prints its name,  but the SAY method for a Person might print "Hello, I'm <name>". How should this be implemented? We could have the SAY method of Person call its own NAME method, which will then be handled by the superclass.

Or we could have the SAY method of Person call the SAY method of Thing. This is where it gets tricky. We'll have to call this method on a different object -- the Thing object. But we must still ensure that when that method executes, if it calls any method, the version of Person is used (if there is one). This is called delegation, and is achieved by passing to the method of Thing the Person object.


Understanding the Code: Methods

For the remainder of the recitation, we looked at code and discussed it.

(define no-method
  (let ((tag (list 'NO-METHOD)))
    (lambda () tag)))

(define (make-named-object name)
  (lambda (message)
    (case message
      ((NAMED-OBJECT?) (lambda (self) #T))
      ((NAME) (lambda (self) name))
      ((SAY) (lambda (self list-of-stuff)
        (display-message list-of-stuff)))
      ((INSTALL) (lambda (self) 'INSTALLED))
      (else (no-method)))))

Can be used directly like this:

(define foo (make-named-object 'george))
((foo 'NAME) foo) ==> george


But with the ask message, can use in more convenient way.

(define (ask object message . args)
  (apply-method object object message args))

Understanding the Code: Inheritance and Has-a


Note how in the INSTALL method, we call a method of the Place on itself.
Also, note how inheritance is handled in the last line.

(define (make-thing name location)
  (let ((named-object (make-named-object name)))
    (lambda (message)
      (case message
         ((THING?) (lambda (self) #T))
         ((LOCATION) (lambda (self) location))
         ((INSTALL)
              (lambda (self)  ; Install: synchronize thing and place
              (ask (ask self 'LOCATION) 'ADD-THING self)
              (delegate named-object self 'INSTALL)))            ;; ignore this for now
 (else (get-method message named-object))))))

This procedure is used to pass the buck to the superobject:

(define (get-method message object)
  (object message))

(define (delegate to from message . args)
  (apply-method to from message args))


Understanding the Code: Multiple Has-a

(define (make-container)
  (let ((things '())) ; a list of THING objects in container
    (lambda (message)
      (case message
 ((CONTAINER?) (lambda (self) #T))
 ((THINGS) (lambda (self) things))
 ((HAVE-THING?)
  (lambda (self thing) (memq thing things)))
 ((ADD-THING)
  (lambda (self new-thing)
    (if (not (ask self 'HAVE-THING? new-thing))
        (set! things (cons new-thing things)))
    'DONE))
 ((DEL-THING)
  (lambda (self thing)
    (cond ((not (ask self 'HAVE-THING? thing))
    (display-message
     (list (ask thing 'NAME) "is not in" name))
    #F)
   (else
    (set! things (delq thing things))
    #T))))
 (else (no-method))))))


Understanding the Code: Multiple Inheritance

A Place is-a Named-Object and a Container.

(define (make-place name)
  (let ((named-obj (make-named-object name))
 (container-part (make-container))
 (exits '())) ; a list of exit
    (lambda (message)
      (case message
 ((PLACE?) (lambda (self) #T))
 ((EXITS) (lambda (self) exits))
 ((EXIT-TOWARDS)
  (lambda (self direction)
    (find-exit-in-direction exits direction)))
 ((ADD-EXIT)
  (lambda (self exit)
    (let ((direction (ask exit 'DIRECTION)))
      (cond ((ask self 'EXIT-TOWARDS direction)
      (error
       (list name "already has an exit to the " direction)))
     (else
      (set! exits (cons exit exits))
      #T)))))
 (else (find-method message (list container named-obj)))))))

This is the procedure that determines which method gets called when more than one superclass has it:

(define (find-method message objects)
  (if (null? objects)
      (no-method)
      (let ((method (get-method message (car objects))))
 (if (not (eq? method (no-method)))
     method
     (find-method message (cdr objects))))))


Understanding the Code: A Tricky Class

How many locations are there here?
What does CHANGE-LOCATION do?

(define (make-mobile-thing name location)
  (let ((thing-part (make-thing name location)))
    (lambda (message)
      (case message
        ((MOBILE-THING?) (lambda (self) #T))
        ((LOCATION)     ; This shadows message to thing-part!
         (lambda (self) location))
        ((CHANGE-LOCATION)
         (lambda (self new-location)
           (ask location 'DEL-THING self)
           (ask new-location 'ADD-THING self)
           (set! location new-location)))
        (else (get-method message thing-part))))))
 



Daniel Jackson
October 20, 1999