10.2.1 Implementing Subclasses

To implement inheritance we change class definitions so that if a requested method is not defined by the subclass, the method defined by its superclass will be used.

The make-sub-object procedure does this. It takes two inputs, a superclass object and the object dispatching procedure of the subclass, and produces an instance of the subclass which is a procedure that takes a message as input and outputs the method corresponding to that message. If the method is defined by the subclass, the result will be the subclass method. If the method is not defined by the subclass, it will be the superclass method.

(define (make-sub-object super subproc)
  (lambda (message)
    (let ((method (subproc message)))
      (if method method (super message)))))

When an object produced by (make-sub-object obj proc) is applied to a message, it first applies the subclass dispatch procedure to the message to find an appropriate method if one is defined. If no method is defined by the subclass implementation, it evaluates to (super message), the method associated with the message in the superclass.

References to self. It is useful to add an extra parameter to all methods so the object on which the method was invoked is visible. Otherwise, the object will lose its special behaviors as it is moves up the superclasses. We call this the self object (in some languages it is called the this object instead). To support this, we modify the ask procedure to pass in the object parameter to the method:

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

All methods now take the self object as their first parameter, and may take additional parameters. So, the counter constructor is defined as:

(define (make-counter)
  (let ((count 0))
    (lambda (message)
      (cond
       ((eq? message 'get-count) (lambda (self) count))          
       ((eq? message 'reset!) (lambda (self) (set! count 0)))
       ((eq? message 'next!) (lambda (self) (set! count (+ 1 count))))          
       (else (error "Unrecognized message"))))))

Subclassing counter. Since subclass objects cannot see the instance variables of their superclass objects directly, if we want to provide a versatile counter class we need to also provide a set-count! method for setting the value of the counter to an arbitrary value. For reasons that will become clear later, we should use set-count! everywhere the value of the count variable is changed instead of setting it directly:

(define (make-counter)
  (let ((count 0))
    (lambda (message)
      (cond
       ((eq? message 'get-count) (lambda (self) count))
       ((eq? message 'set-count!) (lambda (self val) (set! count val)))      
       ((eq? message 'reset!) (lambda (self) (ask self 'set-count! 0)))
       ((eq? message 'next!)  
        (lambda (self) (ask self 'set-count! (+ 1 (ask self 'current)))))
       (else (error "Unrecognized message"))))))

Previously, we defined make-adjustable-counter by repeating all the code from make-counter and adding an adjust! method. With inheritance, we can define make-adjustable-counter as a subclass of make-counter without repeating any code:

(define (make-adjustable-counter)
  (make-sub-object
   (make-counter)
   (lambda (message)
     (cond
      ((eq? message 'adjust!)
       (lambda (self val)
         (ask self 'set-count! (+ (ask self 'get-count) val))))
       (else false)))))

We use make-sub-object to create an object that inherits the behaviors from one class, and extends those behaviors by defining new methods in the subclass implementation.

The new adjust! method takes one Number parameter (in addition to the self object that is passed to every method) and adds that number to the current counter value. It cannot use (set! count (+ count val)) directly, though, since the count variable is defined in the application environment of its superclass object and is not visible within adjustable-counter. Hence, it accesses the counter using the set-count! and get-count methods provided by the superclass.

Suppose we create an adjustable-counter object:

(define acounter (make-adjustable-counter))

Consider what happens when (ask acounter 'adjust! 3) is evaluated. The acounter object is the result of the application of make-sub-object which is the procedure,

(lambda (message)
  (let ((method (subproc message)))
    (if method method (super message)))))

where super is the counter object resulting from evaluating (make-counter) and subproc is the procedure created by the lambda expression in make-adjustable-counter. The body of ask evaluates (object message) to find the method associated with the input message, in this case 'adjust!. The acounter object takes the message input and evaluates the let expression:

(let ((method (subproc message))) \ldots)

The result of applying subproc to message is the adjust! procedure defined by make-adjustable-counter:

(lambda (self val)
  (ask self 'set-count! (+ (ask self 'get-count) val)))

Since this is not false, the predicate of the if expression is non-false and the value of the consequent expression, method, is the result of the procedure application. The ask procedure uses apply to apply this procedure to the object and args parameters. The object is the acounter object, and the args is the list of the extra parameters, in this case (3).

Thus, the adjust! method is applied to the acounter object and 3. The body of the adjust! method uses ask to invoke the set-count! method on the self object. As with the first invocation, the body of ask evaluates (object message) to find the method. In this case, the subclass implementation provides no set-count! method so the result of (subproc message) in the application of the subclass object is false. Hence, the alternate expression is evaluated: (super message). This evaluates to the method associated with the set-count! message in the superclass. The ask body will apply this method to the self object, setting the value of the counter to the new value.

We can define new classes by defining subclasses of previously defined classes. For example, reversible-counter inherits from adjustable-counter:

(define (make-reversible-counter)
  (make-subobject
   (make-adjustable-counter)
   (lambda (message)
     (cond
      ((eq? message 'previous!) (lambda (self) (ask self 'adjust! -1)))
      (else false))))) 

The reversible-counter object defines the previous! method which provides a new behavior. If the message to a adjustable-counter object is not previous!, the method from its superclass, adjustable-counter is used. Within the previous! method we use ask to invoke the adjust! method on the self object. Since the subclass implementation does not provide an adjust! method, this results in the superclass method being applied.