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
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!
- 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
- 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 ...
- 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!