Notes on polymorphism

(Note: I haven't proofread this; if you notice any mistakes, please let me know.
In class on Monday, 10/30, we talked about the Exp example and how we might have an abstract base class (I will use that term rathern than "interface" but note that not all abstract classes are interfaces; why not?) Exp and derived classes such as ExpS, ExpI, ExpP (for sum expression, integer expression and product expression).

There are three items to consider. First, how do we create objects (in practice, rather than as in aritificial scenarios of the kind I considered in class) of type Exp? Second, how does it work internally, i.e., at run-time? And, third, is it worth bothering?

In class I said that there is a particular advantage to doing it this way (beyond the elegance); if you are still thinking about that question, don't read the third item in the list below until you arrive at an answer or are really stuck!

  1. Creating objects: There are two possible approaches. The first is to define a static method called ParseExp in the Exp class and this method will return an Exp object (somewhat similar to ParseId). By the way, I will use our original grammar of expressions here since you can do one-token-look-ahead recursive-descent parsing with that grammar. ParseExp should call ParseFac, a static method of the Fac(tor) class, which will return a Fac object. Next, ParseExp will look at the next token and if it is niether + nor -, it will return the Fac object it obtained by calling ParseFac. If the token is + or -, ParseExp will skip past that, and call ParseExp recursively. When that call returns, we have the factor and the expression making up the overall Exp object. We will then call the constructor of either ExpS class or the ExpM ("M" for "minus") class to construct the appropriate object and return it to the original caller.
  2. How it works/is implemented: When you have an abstract base class such as Exp, and the client code has to construct an Exp object, the client code can't do that directly because the actual object is going to be an instance of one of the derived classes rather than of the base class ... but how will the client decide that? That is why we have the static method ParseExp() in the Exp class to worry about that question. (And ParseFac will be similar.)

    As far as how it works is concerned: once the objects are constructed, it works in the standard polymorphic way. That is, each object will have an extra memory location to hold the address of the appropriate evaluate method that applies to that kind of object; actually, there will be another location to hold the address of the *print* method that applies to that kind of object; etc. And calls to evalExp() or printExp() etc. will be compiled into code that jumps to those respective addresses ...

  3. Why bother with it?: As I said above, don't read this part if you are still thinking about the question of what the advantage of such an approach is! ...
    One of the big advantages of the polymorphic approach is extendibility. That is, you can add a new type of Exp, say conditional expressions (as in C etc.), (as a new derived class) without having to make any changes in any of the existing derived classes; and the existing classes will automatically work seamlessly with the new derived class! But if you use the approach of having a static method called ParseExp as described above, you will have to rewrite that method (to account for the new type of Exp); which means that you will have to recompile the Exp class as well as its (existing) derived classes. On the other hand, if you require the *client code* to construct the Exp objects (rather than have a ParseExp method in Exp to do it), then you don't have to rewrite any of the existing classes. In other words, if the client code has the responsibility of constructing the appropriate objects (whether CondExp or ExpS or ...), then the only change that the person who is responsible for the Exp class has to make is appropriately define the CondExp class. With that change, *previously existing* classes such as ExpS will work happily with the new class; in other words, you can have a sum expression that is a sum of a Fac object and a CondExp object although the CondExp class didn't even exist when we wrote and compiled the ExpS class!