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 neededWe used examples from Problem Set 6.
Inheritance -- passing the buck
Explicitly delegating -- very subtle
Examples of Classes:
Named-ObjectExamples of is-a:
Mobile-Thing
Thing
Container
Place
Person
Thing and Place have names, so each is-a Named-ObjectExamples of has-a:
Place and Person can contain things, so each is-a Container
Person is-a Mobile-Thing
Thing has-a location that is a Place
Container has-a list of Things
We drew a diagram to show these relationships.
Examples of methods:
LOCATION
CHANGE-LOCATION
HAVE-THING?
ADD-THING?
NAME
Which classes do they belong to?
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.
(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))
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))
(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))))))
(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))))))