This theory could be subtitled "Everything you ever wanted to know about Inheritance and Polymorphism but were afraid to ask".
Any discussion about OOP would be incomplete without the following things: (See page 673-674)
It is possible for a language to support encapsulation and polymorphism but not inheritance. For example, C supports encapsulation via the struct keyword and polymorphism via function pointers, but one struct cannot inherit properties from a parent struct.
There are a few other things which we need to discuss regarding inheritance. Please note that in the following list I will use the term "class", although I could just as easily be using the term "record" or "ADT".
The simple definition of inheritance is: a child class aquires traits from a parent class through derivation. This is very similar to the real-life definition of inheritance -- and not by accident.
We have already seen how we can cast some primitive types to similar types (i.e. casting an Integer to a Float). Similarly, we can cast some classes to similar classes along it's inheritance lines.
We'll show how this works in class.
We have already learned about overloaded methods. Overridden methods occur in inheritance trees and are very similar to overloaded methods, but there are some subtle differences between the two:
Many languages support features that prevents you from deriving a new class from an existing class. This effectively makes "terminal nodes" in the inheritance tree.
Likewise, some languages allow a more granular approach to preventing inheritance, allowing the programmer to declare a specific method or property as non-inheritable.
In Java all classes can be derived from by default. Preventing inheritance is done with the final keyword.
In Ada all classes are "terminal" by default unless explicitly declared with the TAGGED keyword.
Polymorphism is a large, many-headed topic. Polymorphism is one of the most advanced features of an object-oriented language because it builds on the more fundamental features of the language.
To simplify the discussion, we will define polymorphism as: "Same interface, different behaviors". To put a little finer point on it: "Invoking the same method produces different results depending on the type of object involved."
Class Exersize: Use example of a child's busybox with a row of similar buttons that all produce different results when pushed.
Here's an example of polymorphism in the real world: All regular files and devices in a UNIX are treated as files. The following examples illustrate how polymorphism is achieved in this environment.
Note that the common interface cat [source file] > [destination file] is used throughout but a different result is produced depending on the destination file. Neat, eh?
When we talk about "polymorphism", we could be referring to one of four things:
Typically when people use the term "polymorphism" without qualifying it, they are referring to dynamic dispatching.
In basic terms, what dynamic dispatching involves, is the ability to dynamically call an overridden function in a child class from a pointer to a base class, thusly:
Note that by its very nature, this dynamic dispatching must be done at run-time.
Here is some Python code that shows how dynamic dispatching works.
Multiple inheritance can get a little thorny when you try to be polymorphic. Consider the following scenario:
Question: Which version of myMethod() will actually be called in this scenario? It will be one of the Derived* class' implementations, but which one?
This situation is known as the "Deadly Diamond of Death". And as you can see, it's a little tricky. This is one reason why some languages choose not to support multiple inheritance.
Python has a really clean, simple way of dealing with the above scenario. Run-time dynamic dispatching is resolved by searching the inheritance list, left-to-right, depth-first. This code listing shows how the "Deadly Diamond of Death" is neatly handled in Python.
(p. 675) Tagged Records are how Ada implements derivation and inheritance among ADTs.
Tagged Records are an improvement over variant records (see p. 675 for a brief review of the 'Employee' variant record). The problem is this: Whenever you want to add a new "flavor" to your variant record, you must modify the case statement inside the variant record and all code that uses it.
Tagged Records, on the other hand, allow you to create an "inheritance tree" which contains base ADTs and derived ADTs, thereby eliminating the problem of constantly needing to modify internal code. See page 676 for an explanation of how to decleare tagged records and inherit new records.
Note that if you do not explicitly declare a record as TAGGED you cannot inherit from it. Ada follows the policy "that which is not explicitly allowed is denied".
(p. 697) This shows how you "up-cast" and "down-cast" from one type of ADT to another which are found in the same inheritance tree. Note the small little ADT heirarchy at the beginning of that section, which is used in the following examples. The good news is, it looks just like normal casting with the syntax: Casting(BeingCasted);
Note that when you up-cast (or "up-convert", as they say in the book) you may lose some of the values in the properties contained in your child class that are not found in the parent class. This does not occur with inherited properties because they are found in both classes.
When you down-cast (or "down-convert") you will not have values assigned to the properties found in the child class because these properties do not exist in the parent. Ada has a mechanism for supplying values to child class fields durring a down-cast which is really quite nice (bottom of p. 677).
(pages 678-689) - This lenghty collection of listings shows how you create derived (or "child") ADTs from a parent ADT. It uses the Person, Employee, etc. ADTs from the casting example. This example code is worth reading if derivation is unclear to you. Note also that in these examples, the constructors return Anonymous Classes. This means it creates a class with no name; it's just created on the stack and returned to the calling function.
Inner Packages (throughout the same listing, introduced on p. 678) This shows an occasionally useful way to create "nested" packages. The reasons why you would do this would be to create "private" or non-inheritable methods. This is the same as declaring a private inner class in C++ or Java. This is so you don't accidentally try to, say, make a new Employee using the MakePerson() method. If this seems a little weird to you, you're a very sensible person.
(p. 689) As we have mentioned many, many times before, Ada is a very strongly-typed language. This can prevent you from making some stupid mistakes, but it can also be a real pain when you want to write more interesting programs, like, oh, say, something to do with polymorphism.
Accomplishing polymorphism is one of those problems that requires more flexible constructs, and that means having more flexible pointers. In our previous discussion, we learned that pointers can only point to a variable of a certain type; there were no 'void pointers' like there are in C.
But never fear! Ada supplies 'General Access Types' by giving us pointers which can point not only to a single type, but to:
I'll warn you: the explanation given for General Access Types is a tad confusing. I will do my best in class to go over this in the hopes of illucidating the concept a tad more.
Also, you will note that the book uses the .ALL syntax to dereference the pointer. If a question shows up on a test which says "How do you dereference a pointer in Ada?", the correct answer is ".ALL".
(p. 691) As mentioned in the theory section above, in order to have polymorphism, you need to have flexible base-class pointers which can be assigned derived-class objects. The book calls general-access base-class pointers "Class-Wide types" (the explanation begins at the bottom of page 691). Note the 'Class attribute for specifying all of the ADTs in an inheritance heirarchy.
Basically, to make polymorphism work in Ada, you need to do the following:
Program 16.9 on pages 692-694 shows a program that uses an array of class-wide pointers, fills that array with various derived class instances, and then makes polymorphic method calls on each element. Pay close attention to that FOR loop with the 'Put' statement in it on p. 694, as this is a perfect example of Polymorphism in action. You will do something very similar in assignment 10.
Starting on p. 695 the book talks about using a Heterogenous Linked List instead of an array and making polymorphic method calls on each node. This example is not for the faint of heart. We aren't going to do anything like this.
Be sure to read the 'New Ada Constructs' chapter review on page 698-699 to make sure you didn't miss any of the new syntax in this chapter.
Also, the theory covered on these notes is pretty important. Plan on seeing them on a test.
This week's assignment is on (you guessed it) inheritance and polymorphism!
You will need three files, similar to assignment #7. Names and descriptions of each follow.
This is the specification file for the reptiles package.
To help you get started, I have written the repties.ads file for you. It is right here. You're welcome. Some explanations of the code are included inline as comments.
If you're curious as to why I included a *Length property along with every string property, take a look at the MakePerson constructor that starts at the bottom of p. 685 and continues onto the next page, and the Put method also on page 685. Short answer: It will help you output your strings a little more nicely.
Note: If you don't like some of the identifiers I used, feel free to chane them. I would, however, appreciate if you left 'Reptile', 'Lizard', 'Snake' and 'Frog' as they are.
This is the body file for the reptiles package. Basically, all you need to do is implement the methods declared in the spec file.
Implement the 3 constructors for Frog, Lizard, and Snake. Note that you will need to set the two properties that each derived class inherits from the Reptile base class, name and nameLength, as well as the two properties declared in each derived class.
Implement the 4 move() methods. Note that the move(critter: Reptile) method will never be called and thus does not need any code in it's body; a null statement is all you need. The move() methods for the derived classes should tell you what kind of reptile it is, output the two string properties, and tell how the reptile moves. (Big Hint: "crawl", "slither", and "hop" is what I'm looking for here.)
This is the name of the client (or driver) program that will use the reptiles package. There are several things you need to do in here:
The output should look like this:
Lizzie the lizard with her silver skin, crawls along. Sammy the snake with his sharp fangs, slithers along. Freddy the frog with his long tongue, hops along.
Hint: The listing for Program 16.9 on pages 692-694 will be very valuable to you as you write this program.
I will be looking for the following keywords in the assn10.adb file: ALIASED, TYPE, ACCESS, ALL, ARRAY, FOR.
This assignment is due on December 5th, 2001, the night of the Final Exam!
Reminder: If you have questions, don't hesitate to ask me for help. Take advantage of the lab time next Monday to ask me if the assignment looks good and works like I'm expecting.