class
, as well as a special class constructor root
for base classes to inherit from. Now an object contains a list of
object and method environments--earlier elements in the list are for
subclasses. The new
operation builds these lists in objects.
Finally, send
must now search for a method by first checking
the first method environment in the object's list, then the second,
and so on.
(Class (super class-binds inst-binds methods) (let ((s (eval super env)) (class-env (extend-list env ...)) (meth-env (extend-list empty-env ...))) (make-class s inst-binds meth-env class-env))) (Root () (make-Root)) (New (class) (letrec ((make (lambda (class) (variant-case class (Root () '()) (Class (super inst-binds meth-env class-env) (let ((obj-env (extend-list class-env ...)) (cons (cons obj-env meth-env) (make super))))))))) (make-Object (make (eval class env))))) (Send (obj msg args) (let* ((o (eval obj env)) (vargs (map (lambda (e) (eval e env)) args))) (variant-case o (Object (class-list) (letrec (find (lambda (l) (if (null? l) (error "msg not understood") (let ((m (lookup (cdr (car l)) msg))) (if m (eval (cadr m) (extend-list (caar l) (cons 'this (car m))) (cons SOMETHING-FOR-THIS vargs))))))) (find class-list))))))To bind a value
SOMETHING-FOR-THIS
to this
, we have several choices.
Suppose that we have class A a superclass of B, which is a superclass of C.
If we pick
o
for SOMETHING-FOR-THIS
, then the search for a message sent to this
within any method of A, B, or C, will begin searching from the actual run-time
class of the object. This is called dynamic inheritance.
If we pick (make-Object l)
(which is really the same
object, since the object environment is shared) for
SOMETHING-FOR-THIS
, then the search for a message sent to
this
from class X will start in class X, even if
the object has a more specific type (is of a subclass or subsubclass of X).
This is called static inheritance.
In C++ you get dynamic inheritance for virtual methods, and static inheritance for non-virtual methods. In Java, dynamic inheritance is used for all methods.
Overriding a superclass method in a subclass hides the name of the
superclass method, but you might still like to call that method from
subclass methods. To permit this, we bind the name super
in a similar manner to binding this
. Once again, there
seem to be two choices: super
could be bound to
(make-Object (cdr class-list))
or (make-Object (cdr
l))
. The first yields dynamic inheritance for
super
, while the second yields static inheritance for
super
.
Exercise: figure out how super works in Java, C++, or your favorite object-oriented language.
Now let's consider an example of inheritance. Suppose that color-point
inherits from point
.
(define point (class (Root) ((netix 0)) ((x 0) (y 0)) (Initialize () body) (Draw () body))) (define color-point (class point () ((color 'white)) (Draw () (pixel (Send this Getx) (send this Gety) color))))Suppose we want to override
Move
in color-point
so that colored points are reset to white before being moved. To do this,
we simply reset the color, and send Move
to super
:
(Move (mx my) (set! color 'white) (send super Move))Now suppose we want to add a method
OnScreenMove
to all points that both
moves a point and redraws it. We add this method to point
so that colored
points will inherit it:
(OnScreenMove (nx ny) (send this Move) (send this Draw))))The method
OnScreenMove
does different things if dynamic
inheritance or static inheritance is used, because it invokes different Move
and Draw methods.
A class may be "like" several other existing classes, so it should inherit from all of them. Suppose C inherits from both A and B. This creates several problems. First: what if a method M is in not in C, but in both A and B? To resolve this name class, Eiffel insists one of the methods be renamed. In C++ the compiler will issue an error indicating an ambiguous reference. Second, if both B and C inherit from A, and D inherits from B and C, how many copies are there of the instance variables of A? Are the replicated or shared? Since Eiffel forces renaming, the instance variables of A are replicated. In C++ the user can specify if there should be two copies (default) or just one (called virtual inheritance).
Multiple inheritance makes creation of the method table difficult. If C inherits from A and B, then C's method table must contain all methods of both A and B. The problem here is that the table can become very large. Consider the following inheritance diagram.
A B D E ^ ^ ^ ^ |- C -| |- F -| ^ ^ |---- G ----|Objects of class G contain complete copies of the dispatch tables for all of A, B, C, D, E, and F.
What is the type of an object? Two answers seem possible: (1) an object is like a record, its type could be a list of instance variables and types and method names and their argument types. (2) an object's type is its class name. The problem with (1) is that types become large and the typing rules are quite complicated. so it is essentially a closure. The problem with (2) is that classes must not first-class values, i.e. they can only be declared at the top level.
Most (all?) object-oriented languages in common use use the second solution.
Here are typing rules for send
and new
using
this approach:
A |- e[0] : C C has M: (t[1] ... t[n] -> t) A |- e[i] : t[i] ------------------------------------------------------------------ A |- (send e[0] m e[1] ... e[n]) : t A |- new C : CConsider
(if p (new C) (new D))
. If C is a subclass of D, we
could let this expression have type D, since C has all the methods of D.
This suggests the following sub-classing rule.
A |- P : bool A |- (new C) : D A |- (new D) : D --------------------------------------------------- A |- (if P (new C) (new D)) : DWe can not use methods of class C not in D on result of this expression, but this is ok: the result might be a D.
This sub-classing rule leads to multiple inheritance and its
attendent problems. Suppose you have classes
C and D, with C a subclass of D. You have a class Sortable with
methods
sort and compare
. To use Sortable,
you must inherit from it, override
compare, and call
Sortable.sort. If you want to sort objects of class D, then D must
inherit from both C and Sortable.