ENCAPSULATION & INHERITANCE
Encapsulation is the cornerstone upon which object oriented programming is built, and without which it would not exist. We will cover the topic of encapsulation in this chapter in enough depth to illustrate its use and what it can do for you in software development. Because there are many new terms in this chapter, you could very easily become intimidated, and wish to simply give up on this new topic. You can be assured that the time spent studying encapsulation will be greatly rewarded as you apply this new technique in your software development efforts.
Object oriented programming is not a panacea to solve all of your software problems, but it is a new and improved way of programming. In fact it is really more of a software packaging technology than a new method of programming. You will find that your software will be easier to write and debug as you gain experience using this new packaging method. Like any new endeavor however, it will require some effort on your part to master these concepts.
If you are using an older version of TURBO Pascal you will not be able to compile and execute the example programs in this chapter. Versions 5.5 and newer can be used, and as mentioned earlier, it would be worth your effort to upgrade to a newer version so you can learn these techniques to improve the quality of your programs.
OUR FIRST ENCAPSULATION
Example program ------> ENCAP1.PAS
The example program named ENCAP1.PAS contains our first example of encapsulation. In order to keep it easy to understand, it was kept very short. This results in a program that does not illustrate the advantage of using object oriented programming, but it does give us a start in the right direction. With this in mind, load ENCAP1.PAS and we will study the code contained in it.
Line 5 has our first new reserved word, object. This is used in much the same way that the reserved word record is used, but it has a much different meaning. An object is permitted to have not only data embedded within it, but also procedures, functions, and constructors. Constructors will be described in detail later. Since data plus procedures and functions can be grouped together in this fashion, the object is said to be encapsulated. An object is therefore a group of related data and the subprograms that operate on that data, all entities being very closely coupled together.
WHAT IS A METHOD?
A method is a term used with object oriented programming, and for the time being we will simply say that a method is either a function or a procedure (including a constructor). A method is therefore a method for doing an operation on some data. Lines 8 through 10 are method headers and give the pattern for all calls to these methods which can be used by the compiler to check for the correct number and types of parameters.
Once again, we promise to discuss the constructor soon. For the time being, simply think of it as another procedure.
The entire object type definition is given in lines 5 through 11. This object contains two variables named length and width, each of type integer, and three methods which can be used to operate on the two variables. In the same manner that the definition of a type in Pascal does not actually give you a variable to use, only a pattern, the definition of an object type does not give you an object. We will declare the objects when we get to line 30 of this program.
You will note that we are already using new terminology, but this is necessary. The field of object oriented programming has its own vocabulary and in order for you to understand technical articles in this field, you must begin now to learn the new terminology. It won't be too long until you feel somewhat comfortable with it.
THE METHOD IMPLEMENTATIONS
The object type definition describes in detail what we can do with the object but we must now describe what actions will take place when each of the methods is called. The implementation for each method will define the operations for that method and are defined in lines 13 through 28 of this example program. The only thing that is really different about these methods is the way their headers are defined. The inclusion of the object type name Box dotted to the method name in the header, is the only difference. This is required, because the method is a part of this object.
The observant student will also notice that we are referring to the object variables within the methods of that object without the object name dotted to the variable name. This is because the implied object name is automatically "with"ed to the variable names within the method implementations, allowing the variables to be directly referred to within the objects because of the definition of object oriented programming. It should be obvious that any mathematics or logical operations can be done within the implementations of the methods. In fact, you can perform any legal Pascal operations within the methods, just like you can in any Pascal function or procedure. Very short operations were selected here because we wish to emphasize and illustrate the interfaces to the methods at this point in the tutorial.
AN INSTANCE OF AN OBJECT
We need another new term at this point. When we use the object type to declare variables of that type as we do in line 30, we are creating instances of that object type. An instance is like a variable in conventional Pascal (non object oriented), except that it can do more and has some very interesting properties that a simple variable does not have. In line 30 we have created three instances of the object type named Box and each has two simple variables associated with it. Three methods are available which can be called to operate on these variables. We therefore have three objects named Small, Medium, and Large. In order to initialize the values stored within the objects we call the three objects in lines 34 through 36 to store values in their internal variables by dotting the name of the object to the name of the method we wish to call. You will note that this looks like the same technique we use to refer to the fields of a record. We display the area of the three boxes in lines 38 through 40 using the same technique used to initialize the values stored, and the program is complete.
We seem to have accomplished very little with this program that we could not have more easily accomplished with an even shorter standard Pascal program, and that is true. This program is only meant to introduce some of the mechanics of object oriented programming. Additional programs will be used to illustrate some of the uses of this new technique.
You may note that we switched terminology halfway through the above paragraphs. We began by referring to the object types as object types and calling the variables declared in line 30 instances. Later we began calling the instances objects. In this tutorial we will refer to the types as object types and the variables either as objects or instances. This terminology is consistent with current practice and should help you learn the new terminology.
Another very important point is the fact that we pass a message to a method rather than call a subprogram as in conventional Pascal. The difference is rather subtle, but there really is a difference as we will see a little later in this tutorial.
WHAT DID WE ACCOMPLISH?
In this program we defined an object type, then declared several instances of that type, one of which was named Small. The object named Small has two internal variables that should only be accessed via its methods, so we will refer to them as private data points. Actually, they should be unavailable to any user outside of the method implementations but Borland chose not to make them private in version 5.5. It is up to you to discipline yourself to not refer to them directly. TURBO Pascal version 6.0 and newer versions, have a way to make the data private. We will study this in the next example program. The proper way to use the object is to send a message to the object telling it to do something to itself. In the case of the Init method, we are telling it to store the two values in its private variables named length and width, and in the case of the Get_Area method, we are telling it to give us the product of its own internally stored width and length which we can then print out.
Remember that in the beginning of this chapter we said that object oriented programming is a code packaging technique. That should help to explain some of the strange things we did in this program. As we continue through the example programs, we will see that everything here was done for a reason and you will eventually learn to use and prefer object oriented programming methods over the old familiar procedural programming method you have been using.
Be sure to compile and execute this program to see if it does what the comments say it will do.
DATA & CODE PROTECTION
The data and methods are protected from outside influence because they are packaged together within an object. Of even more importance is the fact that, because they were to be packaged together, they were probably carefully planned and designed together during the design stage. This would probably result in a more understandable program. The object keeps the data and methods together and keeps them working in close synchronization.
An object type is sometimes referred to as an abstract data type in the technical literature discussing object oriented programming.
Example program ------> ENCAP2.PAS
The example program named ENCAP2.PAS uses most of the same techniques as the last program but this is much more meaningful since it illustrates one of the simplest advantages of using object oriented programming.
In this program, we define two object types in lines 5 through 21, Box and Pole. Each has its own unique kinds of variables associated with it, and each has three methods that can be used with these kinds of data. The method definitions in lines 35 and 46 clearly illustrate why the object name must be associated with the method name in the method implementation. This allows you to use the same method name in more than one object definition. In addition to the two method definitions named Set_Data given here, we could also define and use another procedure with the name Set_Data that was a normal Pascal procedure just like any others we have used in prior chapters of this tutorial.
You will note that in lines 5 through 21 we define the object types which define what each object will do. In lines 23 through 55 we define the method implementations which define how we do it. It is assumed that you know enough Pascal at this point to understand what each method does, so nothing more will be said about the details of this program except for the private types.
THE PRIVATE TYPE
Borland added the private type to version 6.0, but it is not as flexible as it could be. You will notice that the order of declarations within the objects is significantly different in this program. Within an object declaration, you are permitted to have public variables and methods and private variables and methods, but you are required to list all public entities first. The reserved word private is then used with all private entities following its use. Within each section, the variables must come first, followed by the methods.
Even though the variables are listed as private in these classes, they are still available to the main program because they are in the same Pascal unit. This is not very good object oriented programming practice, but it gets the job done. If the objects were placed in a different unit as discussed in chapter 13, the variables defined as private would be unavailable in the calling program. If you are using TURBO Pascal 5.5, you will have to remove the word private and rearrange the variables to put them ahead of the methods in the two object definitions.
In lines 57 and 58, we declare several objects of the defined object types and use some of them in the main program. We can finally illustrate one of the biggest advantages of object oriented programming.
We should all agree that it would be silly and meaningless to multiply the height of one of the poles by the width of a box. If we were using standard procedural programming with all variables defined globally, it would be a simple matter to accidentally write height*width and print out the result thinking we had a meaningful answer. By encapsulating the data within the objects, we would have to really work at it to get that meaningless answer because the system itself would prevent us from accidentally using the wrong data. This is true only if we have agreed not to use any of the data directly but to do all data access through the available methods.
Encapsulation is a form of information hiding, but TURBO Pascal has a rather weak form of it because, as mentioned earlier, Borland chose not to make the variables within the object private in version 5.5. In TURBO Pascal 6.0 the variables can be defined as private variables as mentioned earlier, and are therefore unaccessible outside of the unit in which they are declared. The client would be forced to use only the methods provided by the author of the object to access the contained data. This is true information hiding and adds some degree of protection to the internal data.
If you are using TURBO Pascal 5.5, it is up to you to never refer to the data within the object directly as stated by Borland in the object oriented programming Guide included with the compiler. Even if you are using version 6.0, you must still discipline yourself to not refer to the private data within the defining unit.
The careful student will notice that since all data is carefully tied up within the objects, inadvertent mixing of the wrong data is impossible provided a few simple rules are followed as discussed above. Once again, this is such a small program that it is difficult to see the advantage of going to all of this trouble. In a larger program, once the objects are completed, it is a simple matter to use them knowing that they are debugged and working.
After the data are all printed out, some of the variables are changed in lines 80 through 82, and the same output statements are repeated to print out the same data so you can observe the changes.
A FEW RULES ARE NEEDED
As with any new topic, there are a few rules we must follow to use this new technique. The public variables must all be declared first in the object followed by the public method definitions. The reserved word private is then declared followed by the private variables and finally the private methods. The names of all variables within an object must be unique and may not be repeated as the names of any of the formal variables in any of the methods. Thus length, width, len, and wid must be unique as used in lines 6, 7, 10, and 11. The names of formal variables may be reused in other methods however, as illustrated in lines 6 and 7, and all names may be reused in another object. It should be obvious that all object type names must be unique within a given file and all objects must have unique names.
All of the above rules are obvious if you spend a little time thinking about them. They should therefore not be a stumbling block to anyone with some procedural programming experience.
WHAT IS A CONSTRUCTOR?
It is time to keep our promise and define just what a constructor is. In this present context, that of simple objects, the constructor does very little for us, but we will include one for nearly every object to illustrate its use. The constructor can be named anything desired but it would be best to stick with the convention and name every constructor Init as suggested by Borland. The constructor is used to initialize all values within an object and do any other setup that must be done to use an object. The constructor should be called once for every declared object. When we get to the topic of virtual functions, constructors will be absolutely required for every object, but for the simple objects we are using here, they are optional.
It would be best to include a constructor in every object type, define the constructor to initialize all variables within the object, and call the constructor once for each instance of the object type. Until we get to virtual methods, none of this is required, but it would be good practice to get in the habit of doing it.
WHAT IS A DESTRUCTOR?
A destructor is another method that can be used for cleanup when you are finished with an object. It is usually used in conjunction with dynamic allocation to assure that all dynamically allocated fields associated with the object are deallocated prior to leaving the scope of the object. A destructor is not illustrated in this tutorial but it should be easy for you to define and use one when you have a need for one.
OUR FIRST INHERITANCE
Example program ------> INHERIT1.PAS
Load the example program named INHERIT1.PAS for our first example of a program with inheritance. As always, our first encounter with this new topic will be very simple.
In lines 7 through 14 we define a simple object type defining a vehicle and a few characteristics about the vehicle. We have the ability to store a few values and read them out in several ways. Of course, most of the interest is in the interfaces, so the implementations are purposely kept very small.
In lines 17 through 35, we declare two additional object types that use the Vehicle type as a base for the new types as indicated by the previously defined name Vehicle in parentheses in the object definitions in lines 17 and 26. The Vehicle object type is said to be the ancestor type and the two new object types are called descendant types. The descendant types inherit some information from the ancestor types according to well defined rules. The variables in the ancestor type are all included within the descendant types and are available in objects of the descendant types just as if they had been defined within the descendant types. For that reason, all variable names must be unique within the ancestor type and within each of the descendant types. A name can be reused in one or more descendants however, as is illustrated in lines 18 and 27 where the variable name Passenger_Load is used in both object types.
The method names from the ancestor object types can be repeated in the descendant object types but this has the effect of overriding the method of the same name in the ancestor making the ancestor method unavailable for use in objects of the descendant types. Objects instantiated of the type Car therefore, have the three methods available in lines 11 through 13 of the ancestor type, the constructor in line 19 which overrides the constructor in line 10 of the ancestor type, and the function given in line 22. This object therefore has five different methods to perform its required operations.
Objects of type Truck have five methods available also, the two in lines 11 and 12 and the one in line 33. The two in lines 29 and 34 of the descendant overrides the two in lines 10 and 13 of the ancestor object type.
You should note that even though some of the methods were overridden in the descendant object type, they do not affect the ancestor, and instances of the Vehicle type have two variables and four methods available.
In effect we have an object hierarchy which can be extended to as many levels as necessary to complete the task at hand. The most important part of object oriented programming is the definition of the objects in a meaningful manner, but it is not something you will learn to do overnight. It will take a great deal of practice until you can see the objects in any given project in such a way that a clear solution can be found. I was somewhat intimidated by the clever examples found in a classic text on object oriented programming until I talked to a man that had shared an office with the author at the time he was writing that particular book. I learned that what was finally put in the book was at least the fourth iteration of each problem and in some cases the seventh before he finally arrived at a good solution. We will have more to say about this topic as we progress through this tutorial.
HOW DO WE USE THE OBJECTS?
The implementations of the methods are given in lines 39 through 92 and should be self explanatory, except for a few notable exceptions. You will note that in line 81 we send a message to the Vehicle.Init method to initialize some data. A change to the Vehicle type will be reflected in the Truck type also because of this call. In lines 64 and 65 we are using some inherited variables just as if they had been defined as part of the descendant object types.
In lines 96 through 98 we instantiate one of each and send a message to their constructors in lines 102 through 104 then print out a few of the stored values.
Lines 113 through 116 are repeated in lines 120 through 123 where they are placed within a with section to illustrate that the with can be used for the calls to the methods in the same manner that it is used for accessing the fields of a record. Any other details of this program can be gleaned by the diligent student. Be sure to compile and execute this program so you can verify the given result.
WHY USE INHERITANCE?
Once you have an object that is completely debugged and working, it is possible that it can be reused on your next project. If you cannot use it exactly as is, but are required to make a change to it, you have two choices. You can modify the existing code and hope you don't introduce any analysis or coding errors, or you can inherit it into a new object and add code around the completely debugged object without actually modifying the original code. Several studies have shown that modifying existing code lead to a high probability of introducing errors into the modified code that are difficult to find because you are not as familiar with the code as you should be. Adding a few methods to an existing object, however, is not nearly as error prone and is the preferred method.
AN OBJECT IN A UNIT
Example program ------> VEHICLES.PAS
Load the example program named VEHICLES.PAS for an example of the proper way to package the object so it can be conveniently reused for another project.
The object type definition is given in the public part of the unit so it is available to any Pascal program which needs to use it. The implementation of the methods are hidden in the implementation part of the unit where they are not directly available to any calling program. Note that it is also possible to define a few local methods within the implementation for use only within the implementation but none are illustrated here. There is no body to this unit, only the end statement in line 39 so there is no initialization code to be executed during loading. It would be perfectly legal to include an initialization body, but even if you do, you should be sure to include a constructor to be called once for each object. This is to prepare you for the use of virtual functions which we will study in the next chapter.
Referring back to the discussion of the second example program in this chapter, you will find the means necessary to make the variables truly private in this program. If you move the two variables to a location just after the end of the methods, and add the reserved word private just before the two variables, they will be truly private and unavailable for direct modification outside of this unit. If you are using TURBO Pascal 6.0, you should make this modification and try to access the variables directly in the calling program which will be discussed next. You must compile this unit to disk so it can be used with the rest of the example programs in this chapter.
ANOTHER OBJECT IN A UNIT
Example program ------> CARTRUCK.PAS
The example program named CARTRUCK.PAS continues the new packaging scheme by including two descendant object types in its interface after telling the system that it uses the Vehicles unit in line 6.
The remainder of this unit is constructed just like the last one so nothing more needs to be said about it. Be sure to compile this unit to disk so it will be available for use with the next example program.
Note that this unit could have been further divided into two separate units, one for each object type, but it was felt that it was important to illustrate that several can be combined in this manner if desired. In like manner, the last unit could have been combined with this unit, but once again, it was desired to illustrate the generality of program decomposition and packaging.
USING THE OBJECTS DEFINED IN UNITS
Example program ------> INHERIT2.PAS
Load the program named INHERIT2.PAS for a program that uses the units of the last two example programs and is identical to the program named INHERIT1.PAS.
The only difference in these two programs is in the way the code was packaged. The second way is much more general and more conducive to good software engineering practices because it allows separate development of each of the three program units. Each can be refined independently of the other two and the overall package can be simpler to debug and maintain. It should be clear that any changes to the Car object, for example, will be localized to that single unit and not scattered all over the software terrain.
AN ARRAY AND A POINTER
Example program ------> INHERIT3.PAS
Examine the example program named INHERIT3.PAS for an example of the use of a pointer to an object and the use of an array of objects.
This program is nearly identical to INHERIT2.PAS except for the addition of an array of Car type objects named Sedan to Sedan, and the definition of a pointer to the Truck type object named Semi_Point. Lines 16 and 17 illustrate the initialization of the array of Sedan, and lines 23 through 26 illustrates its use when the data is printed out. An object is dynamically allocated in line 18 and it is then initialized in the next line. Its use is illustrated in lines 28 through 40 and it is deallocated in line 41.
TURBO Pascal 5.5 and newer, have an extension to the New procedure allowing the dynamic allocation and the initialization to take place in the same procedure call. The line;
New(Semi_Point, Init(1, 25000.0, 18, 5000.0));
can be used to replace lines 18 and 19 in this program if you desire to do so.
This program should illustrate that objects can be used with arrays and pointers in the same manner as a record. Be sure to compile and execute this program.
WHAT IS MULTIPLE INHERITANCE?
Multiple inheritance allows the programmer to inherit data and methods from two or more ancestor objects. When this is done however, there is a real problem if there are two variables or methods of the same name and it is up to the programmer to somehow define which will be used by the descendent. Some object oriented programming languages allow multiple inheritance, but most do not. TURBO Pascal has no provision for multiple inheritance, and Borland has made no indication at this time whether future versions will permit it.
WHAT SHOULD YOU DO NOW?
You have reached a major point in your excursion of object oriented programming, because you now have the knowledge you need to do some serious object oriented programming. The best thing for you to do at this point is stop studying and get busy programming, using some of these techniques for your projects. The only topic left is the use of virtual methods and you can easily defer its use for a long time.
One point should be made before you begin a serious programming project. Your first program could have too many objects and be nearly unreadable unless you strive to use only a few objects. After you gain experience, you can confidently use more objects with each programming project. For your first project, define only a few objects and write the majority of the program in standard procedural programming methods. Add a few more objects to your next project and, as you gain experience, you will feel very comfortable with the use of objects and your programming methods will be very clear.
Now is the time to begin using this new knowledge but be sure you enter the water slowly the first time.
Advance to Chapter 15
Return to the Table of Contents