The discussion in this section will focus on Scheme-- and C++. However, you should raise your hand and contribute if you know the "equivalent" phenomenon in Java.
Let's start with an example similar to the previous but slightly different
(define Tall (class () () (Object) ([weight (method () (printf "heavy~%"))] [height (method () (call printHeight this))] [height-weight (method () (call printHeight this) (call weight this))]))) (define Short (class () () (Tall) ([printHeight (method () (printf "short~%)")] [shortheight (method () (call printHeight this))]) (define Light (class () () (Short) ([weight (method () (printf "feathery~%"))] [lightheight (method () (call printHeight this))])
Notice that Tall no longer has a method for printHeight. Additionally note that each class has a method which calls printHeight this. We have also added a method called weight. Consider the following results:
> (define a (Tall)) > (define b (Short)) > (define c (Light)) > (call printHeight b) short > (call shortheight b) short > (call weight a) heavy > (call weight b) heavy > (call weight c) feathery
Now here are some calls which invoke calls to the "virtual" method printHeight:
And now we demonstrate using both printHeight and weight as virtual methods:
> (call height-weight c) short feathery > (call height-weight a) Error in call: Bad Method: printHeight. Type (debug) to enter the debugger. >
Note that when we try to invoke (call height-weight a) we get an error. This is because a is of type Tall and thus has no implementation of printHeight. However, even though Tall itself has no implementation of printHeight it can still call it virtually through the self referential object this. The definition of the methods height-weight and height within Tall depend on printHeight being implemented in some derived class. This is exactly what happens when we call object c: the printHeight method is implemented in the derived class Short.
Borrowing terminology from C++, we say that the method printHeight is being used as a pure virtual function. This is a more specific case of virtual functions in which the superclass calls a method self-referentially for which it has no definition. The method is "purely virtual" because its implementation only exists in derived classes. In C++, we indicate that a function is pure virtual by declaring the function to be virtual (as before) and then setting its value to be zero. For example we would define Tall like this:
class A_class { public: void weight() { cout << "heavy\n"; } void A_height_this() { this->printHeight(); } void A_character_this() { this->printHeight(); this->weight(); } virtual void printHeight() = 0; };
Notice that in C++, we must explicitly declare every pure virtual function we would like to call from the superclass.
Click for some more notes on C++ and virtual functions.
Now let's consider a concrete example using virtual functions. We return to our object oriented graphics system as developed in preceding sections.
Suppose we wish to add a method at the top level which does automated color changes, providing an animated color effect for each object.
We extend the Shape class with a method called animate-hsv. This method takes one argument steps which specifies the number of color steps to animate. The method consists of a loop which iterates steps times, and on each iteration a new color is computed and applied to the object. Load graphics-mm.ss into Petite. Then load your solutions to Exercises 5 and 7. Then try out this new method:
> (load "graphics-mm.ss") > (load "my5and7.ss") > (init-graphics) > (define sq (Square 2.0 2.0 3.0)) > (call draw sq) > (call animate-hsv sq 100) >
Notice how the colors change.
Let's look briefly at the definition of the animate-hsv method:
(animate-hsv (method (steps) (let loop ((i 0)) (when (< i steps) (call color this (hsv->xcolor (exact->inexact (/ i steps)) 1.0 1.0)) (wait 200) (loop (1+ i))))))
The body of the loop calls the color method with the newly computed color and then waits 200 milliseconds before continuing. The color method is used as a virtual function because it is redefined in the derived class Filled-shape. Filled shape overrides color in order to set both the fill and outline colors. For example try the following and note the color behavior:
> (define sq (Square 2.0 2.0 3.0)) > (call draw sq) > (call width sq 0.2) > (call outline-color sq "pink") > (call animate-hsv sq 10)
Note that because we call the color method as a virtual function, both the outline and fill colors change at once.
> (define s (FramedSquare 5.0 5.0 6.0)) > (call draw s) > (call width s .5) > (call outline-color s 'pink)
you should see a large black square with a pink border. Now, however, if you type:
> (call animate-hsv s 100)
you will see the interior of the square going through its myriad colors, but the frame will remain consistently, obdurately, wilfully, stubbornly, obstinately pink.