Since we covered encapsulation and inheritance in the last chapter, we are left with only virtual methods to complete the major topics of object oriented programming. Virtual methods, as they are called in TURBO Pascal, have several other names in the literature to describe the same technique. This technique is sometimes called run-time binding or late binding referring to when the decision is made as to what method will respond to the message. The use of virtual methods moves the responsibility of selection from the client (the logic sending the message) to the supplier (the methods responding to the message). We will begin with a skeleton of a program without a virtual method and add one to show the effect of adding a virtual method.
WITHOUT A VIRTUAL METHOD
Example program ------> VIRTUAL1.PAS
The example program named VIRTUAL1.PAS will be used as the starting point for the study of virtual methods. We must state that this program does not contain a virtual method, it is only the starting point for studying them.
The objects included here are very similar to the objects describing vehicles which we were working with in the last chapter. You will notice that all three objects contain a method named Message in lines 11, 20, and 31. The Message method will be the center of our study in the first three example programs. It should be pointed out that the constructors for the three objects are called in lines 98 through 100 even though a constructor call is still not absolutely necessary in this case. We will have more to say about the constructor calls during the next example program.
Compile and execute the program and you will find that even though it is legal to pass the objects of type Car and Truck to the method named Output_A_Message in lines 109 and 110, the method that is called from line 86 is the method named Message in the parent type Vehicle. This is probably no surprise to you since we defined an object of type Vehicle as a formal parameter of the method Output_A_Message and we would expect it to call the method in the parent type. We need only one small change and we will have a virtual procedure call.
Even though this program seems to do very little, it will be the basis of our study of virtual methods so you should study the code in detail. It is important to note that in lines 108 through 110 we call the same procedure with three different objects and all ultimately call the same object.
NOW TO MAKE IT A VIRTUAL METHOD
Example program ------> VIRTUAL2.PAS
Examine the example program named VIRTUAL2.PAS, and you will find only one small change in the code but a world of difference in the way it executes.
The careful student will notice the addition of the reserved word virtual in lines 13, 22, and 33. This makes the method named Message a virtual method which operates a little differently from the way it did in the last program. Once again, we call the three constructors in lines 100 through 102 and this time the constructor calls are absolutely essential. We will discuss why in a couple of paragraphs.
Once again we send a message to Output_A_Message three times in lines 110 through 112 and line 88 is used to send a message to the Message method. When we compile and execute this program, we find that even though the method Output_A_Message only uses the parent type Vehicle, the system calls the correct procedure based on the type of the actual object passed to this method. The system sends a message to the objects of the correct type instead of to the parent type as we would be inclined to expect. It should be clear to you that the object that is to receive the message is not known at compile time but must be selected at run time when the object arrives at the method Output_A_Message. This is known as late binding since the type is not known until run time as opposed to early binding where the type is known at compile time. Every subprogram call in this entire tutorial, prior to this point, has been early binding.
You will note that even though the method Output_A_Message only knows about the objects of type Vehicle, it has the ability to pass through other types, provided of course that they are descendant types of Vehicle. The method Output_A_Message only passes the message through, it does not do the selection. The selection is done by the objects themselves which answer the messages passed to them. This means that the sender does not know where the message will be answered from, and it is up to the receiver to find that a message is being sent its way and to respond to it. It is often said that the supplier (the method doing the work) must make the decision to answer the message, rather than the client (the user of the work done). The burden is placed on the supplier to do the right thing.
If a method is declared virtual, all methods of that name must also be virtual including all ancestors and all descendants. It is not possible to declare part of the methods of the same name virtual and part standard. All parameter lists for all virtual methods of the same name must also be identical since they must all be capable of being called by the same method call.
ASSIGNING DESCENDANTS TO ANCESTORS?
It is legal in any object oriented language to assign a descendant object to an ancestor variable but the reverse is not true. A vehicle, for example, can be used to define a car, a truck, a bus, or any number of other kinds of vehicles so it can be assigned any of those values. A car on the other hand, is too specific to be used for the definition of anything but a car, so it cannot have any other value assigned to it. A vehicle is very general and can cover a wide range of values, but a car is very specific and can therefore only define a car.
WHY USE A CONSTRUCTOR?
The constructor is absolutely required in this case because of the way the authors of TURBO Pascal defined the use of virtual functions. The constructor sets up a pointer to a virtual method table (VMT) which is used to find the virtual methods. If there is no pointer, the system jumps off to some unknown location and tries to execute whatever happens to be there and could do almost anything at that unknown and undefined point in the code. So it is important to call a constructor once for each object as is done here so the pointer to the VMT can be initialized to the proper value. If you make several objects of one type, it is not enough to call a constructor for one object and copy that object into each of the other objects. Each object must have its own constructor call in order to prevent a system crash.
The strange looking code in line 6 tells the system to check each call to a virtual function to see if the constructor has been called. This slows the program down slightly but will result in an error message if a virtual method is called prior to its VMT being properly set up with a constructor call. After a program is thoroughly tested, the code can be removed from line 6 to speed up the program slightly by eliminating the checks. Be warned however, that a call to a virtual method without A VMT will probably result in the computer hanging up.
VIRTUALS AND POINTERS
Example program ------> VIRTUAL3.PAS
The example program named VIRTUAL3.PAS is nearly identical to the last program except that this program uses pointers to objects instead of using the objects directly.
You will notice that once again, the methods named Message are all defined as virtual and a pointer type is defined for each object type. In lines 99 through 101, three pointers are declared and memory is dynamically allocated on the heap for the objects themselves. The objects are all sent a constructor message to initialize the stored data within the objects and to set up the VMT for each. The rest of the program is nearly identical to the last program except that Dispose procedures are called for each of the dynamically allocated objects. The code used in line 6 of the last program to force a check of each virtual method call has been removed to illustrate that it doesn't have to be there if you are sure a message is sent to a constructor once for each object with a virtual method.
Compiling and executing this program will give the same result as the last program indicating that it is perfectly legal to use pointers to objects as well as the objects themselves.
AN ANCESTOR OBJECT
Example program ------> PERSON.PAS
The example program PERSON.PAS is not a complete program at all but only an object definition within a unit. This unit should pose no problem for you to understand so we will not say much except to point out that the method named Display is a virtual method.
This example program, as well as the next two example programs, have been carefully selected to illustrate the proper way to package objects for use in a clear understandable manner.
Compile this unit to disk in order to make it available for use in the remainder of this chapter.
SOME DESCENDENT OBJECTS
Example program ------> SUPERVSR.PAS
The example program named SUPERVSR.PAS is another unit which contains three descendants of the previously defined object named Person_ID. You will notice that each of the objects have a method named Display which is virtual just as the same method in the ancestor object was.
The interface for each object has been purposely kept very simple in order to illustrate the use of objects. The implementation has also been kept as simple as possible for the same reason so the diligent student should have no trouble in understanding this unit completely.
Once again, be sure to compile this unit to disk in order to make it available for use in the next few example programs.
A COMPLETE EMPLOYEE PROGRAM
Example program ------> EMPLOYEE.PAS
Although the program named EMPLOYEE.PAS is a very short program that does very little, it is a complete program to handle a small amount of data about your employees.
You will notice that we declare an array of ten pointers to the Person_ID object and one pointer to each of the three descendant objects. In the main program we send a message to the constructor for each of the array elements. Inspection of the Person_ID.Init code will reveal that this initialization does nothing. It is used to initialize the pointer to the VMT for each object, so the message must be sent. We then dynamically allocate six objects of assorted descendant objects being careful to send a message to the constructor for each object. This is done to generate a VMT for each object as it is allocated. Finally, we send a message to the first six objects pointed to by the array of pointers instructing them to display their values.
When the program is compiled and executed, we find that the virtual methods were called as explained in the last example program. Even though only one kind of pointer was passed to the Display method, three different messages were actually displayed, each message being of the proper kind based on the type of pointer used.
You will notice how clean and neat the main program is. It is extremely easy to follow because all of the implementation details have been moved to the objects themselves. Once the objects are carefully defined and debugged, the main program is usually a snap to write and debug.
Object oriented programming requires a whole new mindset over the procedural methods you have been using but after you catch on to the technique, you will find your programs much easier to debug and maintain. The one thing you should avoid is the use of too many objects in your first program. It is best to define a few simple objects for your first attempt at object oriented programming and write the rest of the program using standard procedural methods. Then as you gain experience, you can begin using more and more objects until you finally write a program that is essentially all objects. Of course, you will find that you will always write at least part of your program in a standard procedural format as was done in EMPLOYEE.PAS in this chapter.
Advance to Chapter 16
Return to the Table of Contents