Chapter 15 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. OUR FIRST ENCAPSULATION ____________________________________________________________ The example program named ENCAP1.PAS contains ============== our first example of encapsulation. In order ENCAP1.PAS 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 Page 15-1 Encapsulation and Inheritance 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 IMPLEMENTATION ____________________________________________________________ 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, namely the inclusion, in the header, of the object type name Box dotted to the method name. This is required, but we will wait until the next example program to define why it is needed. 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 Page 15-2 Encapsulation and Inheritance we wish to 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 and additional programs will be used to illustrate some of the uses of this new technique. NEW TERMINOLOGY ____________________________________________________________ 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. Page 15-3 Encapsulation and Inheritance 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. It is therefore up to you to discipline yourself to not refer to them directly.) 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 with an object. Of even more importance is the fact that because they were to be packaged together, they were probably carefully thought out together during the design stage. This would probably result in a much 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. Page 15-4 Encapsulation and Inheritance MORE ENCAPSULATION ____________________________________________________________ The example program named ENCAP2.PAS uses ================ most of the same techniques as the last ENCAP2.PAS 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 19, 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 33 and 44 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. Using the same method name in several places is often referred to as name overloading in object oriented programming terminology. You will note that in lines 5 through 19 we define the object types which define what the object will do. In lines 21 through 53 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. In lines 55 and 56, 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 it is a rather weak form of it in TURBO Pascal because, as mentioned earlier, Borland chose not to make the variables within the object private. If the variables were defined as private Page 15-5 Encapsulation and Inheritance variables, they would be unaccessible outside of the implementation for the object and the client would be forced to use only the methods provided by the author of the object to access the contained data. This would be true information hiding and would add some degree of protection to the internal data. It is up to you to never refer to the data within the object directly as stated by Borland in the OOP GUIDE included with the compiler. 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 78 through 80, and the same output statements are used to reprint 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 variables must all be declared first in the object followed by the method definitions. 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, and 8. The names of formal variables may be reused in other methods however, as illustrated in lines 8 and 9, 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. Page 15-6 Encapsulation and Inheritance 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, use 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 ____________________________________________________________ Load the example program named INHERIT1.PAS ================ for our first example of a program with INHERIT1.PAS 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 is said to be the ancestor type and the two new object types are called descendant types. The descendant Page 15-7 Encapsulation and Inheritance 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 as we progress through this tutorial. Page 15-8 Encapsulation and Inheritance 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. AN OBJECT IN A UNIT ____________________________________________________________ Load the example program named VEHICLES.PAS ================ for an example of the proper way to package VEHICLES.PAS 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 if you do, 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. It will be necessary for you to compile this unit to disk so it can be used with the last example program in this chapter. Page 15-9 Encapsulation and Inheritance ANOTHER OBJECT IN A UNIT ____________________________________________________________ The example program named CARTRUCK.PAS ================ continues the new packaging scheme by CARTRUCK.PAS including the two descendant object types in ================ its interface after telling the system that it uses the Vehicles unit. 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 ____________________________________________________________ Load the program named INHERIT2.PAS for an ================ example that uses the units of the last two INHERIT2.PAS 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 ____________________________________________________________ Examine the example program named ================ INHERIT3.PAS for an example of the use of a INHERIT3.PAS 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[1] to Sedan [3], and the definition of a pointer to the Truck type object named Semi_Point. Lines 16 and 17 illustrate the Page 15-10 Encapsulation and Inheritance 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 has 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 just like 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 (version 5.5) has no provision for multiple inheritance, and Borland has made no indication at this time whether future versions will allow it. WHAT SHOULD YOU DO NOW? ____________________________________________________________ You have reached a major point in your excursion of object oriented programming, because you now have most of 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 and 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 until you gain experience. Define only a few objects and write the majority of the program in standard Page 15-11 Encapsulation and Inheritance 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 enter the water slowly the first time. PROGRAMMING EXERCISES ____________________________________________________________ 1. Modify ENCAP2.PAS in such a way to multiply the height of the short pole times the length of the medium box and print the result out. Even though this is possible to do, it requires you to expend a bit of effort to accomplish. Remember that you should not use the components of an object directly, only through use of the available methods. 2. Add an object named Pick_Up to INHERIT2.PAS of type Truck and initialize it to some reasonable values. Print out its loading and efficiency in a manner similar to the Semi. Page 15-12