Objects

Objects are generalizations of structures: Example:
(define-class Dog%
   [(constructor-args name)
    (public sound speak)]
   (define sound "arf")
   (define (speak) (string-append sound sound)))

(define my-dog (make-object Dog% 'rover))
(send my-dog speak) = "arfarf"

Inheritance

: a class "Animal".

All information about (generic) animals should be localized in that class; anything specific to Dogs should be in Dog. We say the subclass Dog inherits from (extends) its superclass, Animal. Let's add one more item, a boolean (our zoo isn't very good).

; Define the base class.
;
(define-class Animal%
  [(constructor-args name)
   (gettable name)          ; Bug: constructor-args can't be "public" :-(
   (public sound speak)]
  (define (speak) (string-append sound sound))
  (define sound "unknown"))

(define x (make-object Animal% 'nellie))
(send x speak)
(send x get-name)  ; This method generated due to name being "gettable".
;
; Note that "send" is the same as java's "."
; (except "send" is a regular function, not special syntax
; to call a function).



; Define a subclass.
;
(define-class Dog%
  [(extends Animal%)
   (override sound)]
  (define sound "woof")
  
  ; The variable "this" is bound:
  ;(printf "say ~s." (send this speak))
  )
(define rover (make-object Dog% 'rover))
(send rover speak)
Note that if you forgot to say that you were *overriding* sound, then sound would be a private variable to Dog%, and Animal%'s speak would still use Animal%'s sound, rather than Dog%'s sound. Thus you need to inidcate "hey, here in Dog%, this variable sound is meant to override the older definition up in Animal%." Hence the keyword "override".

We can override methods as well as fields (since they're each just placeholders):

; Define a subclass which overrides the method "speak".
;
(define-class Cat%
  [(extends Animal%)
   (override sound speak)
   ]
  (define sound "meow")
  (define (speak) ""))
(define morris (make-object Cat% 'morris))
(send morris speak)

We've seen subclasses which modify their base-class behavior; here's an example of a subcalss which adds more new behavior:

; Define a subclass which extends the base with new info.
;
(define-class Bird%
  [(extends Animal%)
   (override sound)
   (public wingspan)]
  (define sound "tweet")
  (define wingspan 3))

(define tweety (make-object Bird% 'tweety))
(send tweety speak)

Why is object-oriented a win, over just having defined all these as structures? Because a single function works on all of them:

(map (lambda (x) (send x speak)) (list rover morris tweety))
;
; Note the polymorphism: one function "speak"
; works on different types (Bird%, Cat%, ...).
; It will even works on classes that haven't been 
; defined yet (like Iguana%).
Suppose two different people are writing each of Animal% and Iguana%. They both benefit: When Animal% is re-written, Iguana%s will automatically get the improvements; and conversely the person writing Animal% never needed to know what future subclasses will be -- their code will work for those subclasses!

Aisde: A couple further notes about scheme's objects:

Abstract classes

Going back to our "class hierarchy":

          Animal
       /  |   |   \         ...
     Dog Cat Bird Alligator ...
Note that in our model, it is inconceivable to have an Animal without it being a specific subclass of Animal (a Dog, or ...). You can't just have an amorphous Animal sitting there. So while "new Dog(...)" is fine, saying "new Animal(...)" is not something we want to allow. We say that Animal is "an abstract class". Scheme doesn't have syntax to enforce that, although some languages do.

Rolling your own "abstract"

A quick hack, to do this: In Animal, define a method "i-am-abstract", which causes an error when called. From the body of the class, call that function (so that it causes the error if somebody tries make-object Animal%).

If you want have Dog% to subclass Animal%, then the Dog% must *override* that method "i-am-abstract" to do nothing. When doing a make-object Dog%, this implicitly does a make-object Animal%, but when doing Animal%'s initialization it calls the overridden versino of "ia-ma-abstract", and so that initialization works fine!

A similar trick can be used to declare particular methods as abstract (e.g. that anybody subclassing *must* override those methods. Really, we want Animal%'s "sound" to be abstract; as it is one can make a subclass of Animal% which doesn't define its own "sound", which is bad.

Another example of wanting an abstract method

We have already seen: classes, objects, inheritance of superclass code. You can also have a method defined as abstract, e.g. inside Animal%, you might want an abstract method "actAngry". Since different animals act angry in very different ways, we can't have one general case that covers the possibilities. This abstract method means that any (non-abstract) subclass must have a version of actAngry(). It means that "hey, no code provided here, so any (non-abstract) class descended from this class *must* implement this function." (This implicitly makes the entire class abstract.) Used when you know the class should do this, but don't know how.

What is the advantage of this? Well, it means that any code using an Animal can count on it knowing all the Animal methods:

;; giveToSpoiledMeanKid: Animal% --> (void)
;;
(define (giveToSpoiledMeanKid pet ) 
  (begin (printf "Here kid, here is ~s.~n" (send pet get-name))
         ; The kid gets to work, annoying the pet to death:
         (send pet actAngry)
         (set-pet-alive?! false)))
Note that this code is correct as it stands, even though we can't run it (because we need a particular animl with a particarly way of acting angry). (In fact, it might be for a type of Animal% that hasn't been invented yet!)

Even better, later people can decide to add a new variant of Animal (say, hamster), and we don't need to re-write giveToSpoiledMeanKid, and a hundred other functions involving Animals. Non-object-oriented approaches didn't give us the flexibility that inheritance does.


The complete code from lecture:
(load "/home/comp210/Lectures/define-class.ss")
;
; NB Must be at Full Scheme language level, to load this.
; (Probably should be a teachpack instead, sorry. --ian)




; Define the base class.
;
(define-class Animal%
  [(constructor-args name)
   (gettable name)          ; Bug: constructor-args can't be "public" :-(
   (public sound speak)]
  (define (speak) (string-append sound sound))
  (define sound "unknown"))

(define x (make-object Animal% 'nellie))
(send x speak)
(send x get-name)  ; This method generated due to name being "gettable".
;
; Note that "send" is the same as java's "."
; (except "send" is a regular function, not special syntax
; to call a function).



; Define a subclass.
;
(define-class Dog%
  [(extends Animal%)
   (override sound)]
  (define sound "woof")
  
  ; The variable "this" is bound:
  ;(printf "say ~s." (send this speak))
  )
(define rover (make-object Dog% 'rover))
(send rover speak)


; Define a subclass which overrides the method "speak".
;
(define-class Cat%
  [(extends Animal%)
   (override sound speak)
   ]
  (define sound "meow")
  (define (speak) ""))
(define morris (make-object Cat% 'morris))
(send morris speak)

; Define a subclass which extends the base with new info.
;
(define-class Bird%
  [(extends Animal%)
   (override sound)
   (public wingspan)]
  (define sound "tweet")
  (define wingspan 3))

(define tweety (make-object Bird% 'tweety))
(send tweety speak)


; Note the polymorphism: one function "speak"
; works on different types (Bird%, Cat%, ...).
; It will even works on classes that haven't been 
; defined yet (like Iguana%).
;
(map (lambda (x) (send x speak)) (list rover morris tweety))