OBJECT ORIENTED PROGRAMMING
In recent years, object oriented programming has seen a tremendous rise in popularity. It seems like the term has been applied to every programming language and marketing scheme regardless of whether it is actually a part of the product. There are probably very few persons that really understand what it is and know how to properly use object oriented programming, but that will not prevent us from digging in and learning how to use some of the technique in our programs. We will devote the next two chapters to this subject.
It is beyond the scope of this tutorial to define and illustrate such topics as object oriented analysis and object oriented design. There are lots of books available that cover those topics quite well, so we will spend our time showing how the various constructs available in Ada 95 help us to build a more reliable program.
The three terms that are usually applied to object oriented programming are encapsulation, inheritance, and polymorphism or dynamic binding. We have already covered a good bit of encapsulation, which can properly be called information hiding, in chapter 21 of this tutorial, and we will defer our discussion of polymorphism until chapter 23. This leaves us with inheritance which we will cover at this time.
INHERITANCE AND EXTENSION
Inheritance involves making a copy of some existing entity and adding to it to define an entity with all of the properties of the original, but with added properties. Most other programming languages approach this topic with the emphasis on the inheritance portion of the previous statement, with little emphasis on the extension operation. Ada writers, however, place the emphasis on the extension portion of that statement, so we will too. In this chapter we will illustrate how to begin with an entity and extend it such that it has additional capability. As usual we will start with a simple example program.
THE SIMPLEST INHERITANCE
Example program ------> e_c22_p1.ada
This program has nothing to do with object oriented programming as usually applied to programming languages, but it illustrates simple extension of a few Ada types. Line 4 declares that the type MY_INTEGER will have all of the properties of the type INTEGER but with a more limited range, so it is actually inheriting all of its properties from the parent type. The same statement can be said about the type MY_FLOAT in line 6. This inheritance is not very interesting however, so we will go on to a more complex type.
We define a type in lines 8 through 13 named WOODEN_BOX which is composed of three simple components and has several predefined operations which all records have, such as assignment, compare for equality, and compare for inequality. We add another primitive operation, the ability to add two objects of this type by overloading the + operator in line 15. In line 17, we derive a new type named STEEL_BOX which will have all of the components and operations that its parent type has, including the overloading of the + operator. The interesting part is in line 19 where we extend the STEEL_BOX type by overloading the - operator. It should be clear that we have "inherited" all of the functionality of the WOODEN_BOX type, and extended the operation of the STEEL_BOX by the additional operator.
This is inheritance and extension, but it is very limited because we cannot add components to the inherited capability, only operations. We will add components in example programs later in this chapter, but this program fragment was given to illustrate a very basic form of inheritance.
MORE SIMPLE TYPE EXTENTION
Example program ------> e_c22_p2.ada
The example program e_c22_p2.ada illustrates a little more extension. It begins with a definition of a very simple record named TRANSPORT, with two components and three explicitly declared subprograms in addition to the operators that are generated automatically by the system such as assignment and compare for equality. There is nothing magic or unusual about this record. It can be used to define data and used according to the rules of Ada just like any other record we have studied in this tutorial. In fact we will use it in the next example program.
The next record, named CAR, is a little more interesting because it is declared as a derived type of the TRANSPORT record. This means that it has all of the components and functionality of its parent. In fact, we can use this new type as if it has the three functions declared in lines 11 through 15 redefined with the first parameter of type CAR. All three of these subprograms can be called directly with a variable of type CAR as the first parameter, without doing any type conversion on the variable. We extend CAR by adding a function named Tire_Loading to give it more capability than its parent type. This truly is inheritance and extension in the fullest sense of the terms, but we still do not have the ability to add components to the new type, only functionality. We will be able to extend components shortly.
The implementation in the package body is very simple and you should have no difficulty understanding it with the knowledge of Ada that you have gained already. Note that the three subprograms for the TRANSPORT type are available for the CAR type just as if they were redefined here, but they are not, because they are inherited automatically from the parent type. All operators and primitive subprograms are automatically inherited when we derive a new record from an old record.
WHAT ARE THE PRIMITIVE OPERATIONS?
The primitive operations of a type are;
USING THE TYPE EXTENSION PACKAGE
Example program ------> e_c22_p3.ada
Examine the file named e_c22_p3.ada for an example of using the two types we defined in the last example program. You will notice that the package named Conveyance1 is with'ed and use'ed in lines 3 and 4 to make them available for use in the program. We define two variables in lines 8 and 9, one each of the two types which we will use in the program.
Lines 13 through 18 are rather simple, with nothing new, so you can study those on your own. The same is true of lines 20 through 23, except that we will return to this group of statements shortly.
The first really interesting statement is given in line 25 where we call Set_Values with the first parameter being of type CAR, even though we have no explicitly defined function with that type. This proves that the system truly generated a function with the type CAR as the first parameter that is identical to the same function with the type TRANSPORT as the first parameter. Since we didn't write the code twice, we only have a single function to maintain that operates on both of the types, but if we wanted to have them do different things, we could write a specification and a body for CAR's own Set_Values function.
Type extension is therefore illustrated in this example program in line 25 as well as line 28, where the function Get_Wheels is called, which was inherited from the parent type. Line 30 however, illustrates type extension since we wrote a completely new function to retrieve the Tire_Loading from CAR type variables. You will notice that because of the clear definition of Ada, it is impossible to tell in this program which functions were inherited and which were extended. The TRANSPORT type has no Tire_Loading function.
Object oriented programming includes the concept of information hiding which we are not practicing with this example program. If you refer back to line 21, you will see that we are accessing the number of Wheels on the Hummer directly. This is bad practice, because one of the most error prone operations in computer programming is providing direct access to data over a large range. The principle of information hiding requires that the data for an object be hidden within the object and unavailable to the outside world as discussed in the previous chapter of this tutorial. We will completely hide the data within the object in the next example program.
Be sure to compile and execute this program to be sure you understand what it does before going on to the next example program.
TYPE EXTENSION WITH A PRIVATE SECTION
Example program ------> e_c22_p4.ada
The example program named e_c22_p4.ada is very similar to the previous program with the exception that the two record types are declared to be private. This was done to prevent the user from seeing into the objects and directly manipulating the contained data. However, with each improvement we add to our code, there is the possibility that something else will not work as conveniently, and there is a bit of a penalty to be paid here.
Because the TRANSPORT type is private, it does not provide full functionality to objects outside of itself. In fact, the only operations it permits are assignment and compare for equality or inequality. The three subprograms defined for the TRANSPORT type in lines 6 through 10 are not available to be inherited into the CAR type because there is no indication that CAR inherits from TRANSPORT at this point. It is necessary to declare them explicitly for the CAR type as is done in lines 14 through 18. It is also necessary to provide a full implementation for these three functions as is illustrated in lines 58 through 74. It is clear that this form of inheritance is lacking in elegance.
As with all private types, the actual definitions are given within the private section in lines 21 through 29 where it is clear that CAR inherits all of TRANSPORT. This example is very similar to the last example, so you will be left to study the package body on your own.
USING THE TYPE EXTENSION
Example program ------> e_c22_p5.ada
The example program named Vehicle2 is in the file named e_c22_p5.ada and gives a very limited example of using the TRANSPORT and the CAR class. There is nothing new here except for the fact that the direct access of the Wheels component is not used here since it is now hidden from view.
The next example program will finally provide full inheritance and extension of both components and operations. This is what is usually meant when inheritance is referred to in articles on object oriented programming.
REAL TYPE EXTENSION
Example program ------> e_c22_p6.ada
The example program named e_c22_p6.ada contains our first example of real inheritance and extension and is a good example of the way it is actually used in practice. The last few example programs, even though they are valid Ada programs, do not illustrate real inheritance, but were intended to show that there are more subtle forms of inheritance built into the Ada language.
We begin by declaring a private TRANSPORT type in line 5, but with an extra reserved word added, namely tagged. The word tagged indicates to the Ada compiler that this type may be extended with additional components and/or operations to define a completely new type. The actual definition is given in lines 35 through 39 and should look very familiar by now except for the reserved word tagged which is added here also. Three subprograms are declared in lines 7 through 11 in much the same manner that they have been added in previous programs in this chapter. Except for the addition of the word tagged, this package is identical to the previous example package.
NOW FOR THE REAL DIFFERENCE
When we define the CAR type, things look a lot different than they did in the last program. This time the parent type is mentioned in the declaration along with a new use of the reserved word with. We have been using this word to with packages into our various programs but now it is used to indicate that something new is going to be added to the parent type to generate the new type. Lines 41 through 44 contain the full declaration of the new type CAR. It says to create a new derived type of TRANSPORT with some additions because of the reserved word with being used here. The component named Passenger_Count is included in the CAR type along with the two components from TRANSPORT, giving the CAR type three variables. We have finally found a way to extend the number of components in an inherited type. Note that you can only add components. It is not possible to remove components or to override components.
The CAR type also inherits the three subprograms from TRANSPORT, but it provides its own Set_Values procedure which only sets the value of the Passenger_Count component. Presumably, objects of the CAR type will be required to use the Set_Values procedure provided by the parent class in order to set the values of the two inherited components. This is what will actually be done in the next example program. The CAR type also provides a new subprogram named Get_Passenger_Count to return the number of passengers. Overall, the CAR type consists of three components and provides five subprograms which can be used to manipulate objects of this type.
It is possible to extend a type by adding subprograms, and it is possible to modify the functionality of a type by overriding one or more subprograms, but it is not possible to eliminate subprograms from the new type which are part of the parent type. Even though the TRANSPORT type is private, the CAR inherits all of the primitive operations of the TRANSPORT type because the TRANSPORT type is tagged. This is true inheritance because all of the entities are inherited.
WHAT DOES IT MEAN TO FREEZE A TYPE?
We can add all of the functionality we desire to a type until we use that type to define a variable or inherit it into a new type. After it is used, we are not allowed to add functionality to the type because we would have two variants of the type, the one where it was used and the newer changed version. The two would be incompatible even though they had the same type name. For this reason, the type is "frozen", which means you cannot add more primitive operations to it.
ANOTHER NEW TYPE
Since the TRUCK type is very similar to the CAR type, little needs to be said except that it adds two components to those inherited, and it adds two subprograms to those inherited from the parent. The TRUCK type is a little different because it provides initialization data for all four variables in its procedure named Set_Values, so it has no need for the procedure of the same name in the parent type.
The BICYCLE type is declared in line 31 in exactly the same manner as the CAR and the TRUCK. When we get to line 52 where the private type is actually defined, it looks a little different than the other two new types. The with null record at the end of the line tells the system that the BICYCLE type will have all of the components of the parent type and no more. It is necessary to explicitly tell the system that none will be added.
We now have the ability to add components and subprograms to an inherited type. We can add subprograms without adding any components. We can also add components without adding any additional subprograms, but that would not be very useful because we would have no way to use the new components since they would be hidden in the object.
WHAT IS A CLASS IN ADA?
A class in Ada 95 is a group of types with a common ancestor, and the name of that class is the type name of the highest ancestor. The present example program contains the class named TRANSPORT which consists of the types TRANSPORT, CAR, TRUCK, and BICYCLE. It would also be correct to say that it contains the class named CAR that consists of only the type CAR. Any hierarchy of types compose a class with the class name being the name of the highest class in the hierarchy. The class concept is not very important at this time, but it will become important when we study dynamic dispatching in the next chapter.
THE PACKAGE BODY
The package body, given in lines 58 through 121, is nothing more than the same implementations we have been using in this chapter. All of the inheritance and extension constructs are in the specification part of the code.
There is one small detail that the student should take note of in line 104 and 105, where the components inherited from the parent class are initialized. Nothing new there, but in line 108 the procedure named Set_Values from the parent class is used to initialize the same two variables. Since the types are different, it is necessary to perform a type transformation from the TRUCK type to the TRANSPORT type to get the compiler to accept it. Note that either method of initialization can be used. The two methods were given here only as an example.
USING THE REAL TYPE EXTENSION
Example program ------> e_c22_p7.ada
Examine the file named e_c22_p7.ada which serves as an illustration of how to use these new types we have just generated. As expected, the new package is both with'ed and use'ed into this program, and the four new types are available for use as needed. There is nothing special about the new types, except for the fact that three are descendants of the fourth. They are used just like any other types would be used as can be seen by inspecting this example program. You will notice that two procedure calls are necessary to set all of the values in the CAR type objects, but only one call is necessary to set all of the values in the TRUCK type objects. This is only because of the way we declared them in the package. In a real program it would be best to declare them both in a similar manner to make the code easier to understand.
Be sure to compile and execute this program.
INITIALIZERS AND FINALIZERS - CONTROLLED
Example program ------> e_c22_p8.ada
The example program named e_c22_p8.ada illustrates automatic object initialization and automatic cleaning up when the object is about to be destroyed. We include the Ada.Finalization package for use here, and declare a type named WIDGET which inherits from the type named CONTROLLED. We override three procedures from CONTROLLED with the predefined names Initialize, Adjust, and Finalize, each with one parameter of the type of record we just declared. The package body contains the definitions of the three procedures which are trivial because they each output only a single line of text to the monitor.
In the main program, we include the Component package in the with list, and we write a trivial program that does nothing but output a few lines of text. When we execute this program, we get a bit of a surprise because the three procedures in the Component package are executed even though we never called them, the system did. They are very special because they each have a special job to do.
Initialize - When any object of this type is defined, this procedure is run automatically, without the programmer calling it explicitly. This is the place to initialize the components of the class or include any other initialization code that needs to be executed for each object of this type.
Adjust - Following an assignment, you may have some cleaning up of the data within the new copy. This procedure is called automatically for the new object after the new data has been assigned to it.
Finalize - When you assign something to a variable, the data contained in that variable is overwritten and lost forever. You may wish to do something special with the data before it is overwritten, and that is one of the jobs for this procedure. When you are finished with an object and you wish to destroy it, or it goes out of scope, you will need to do some cleaning up if it has an access variable to something on the heap that you are finished with, or some other job that must be done prior to destroying the object. This procedure is called automatically by the system.
You will notice that the Initialize procedure is executed twice prior to the beginning of the program, because there are two variables. The Finalize and the Adjust procedures are both executed for each of the two assignment operations as is evident from the listing. The Finalize procedure is called once for each of the two objects when they go out of scope at the end of the program.
These three procedures can be a great aid when you are using types that are rather complex.
Return to the Table of Contents