I l@ve RuBoard |
![]() ![]() |
5.1 Object-Oriented Programming ConceptsThe two primary characteristics of object-oriented programming are inheritance and polymorphism. Inheritance allows us to group classes into families of related types, allowing for the sharing of common operations and data. Polymorphism allows us to program these families as a unit rather than as individual classes, giving us greater flexibility in adding or removing any particular class. Inheritance defines a parent/child relationship. The parent defines the public interface and private implementation that are common to all its children. Each child adds to or overrides what it inherits to implement its own unique behavior. An AudioBook child class, for example, in addition to the title and author it inherits from its parent Book class, introduces support for a speaker and a count of the number of cassettes. In addition, it overrides the inherited check_out() member function of its parent. In C++, the parent is called the base class and the child is called the derived class. The relationship between the parent or base class and its children is called an inheritance hierarchy. At a design review meeting, for example, we might say, "We intend to implement an AudioBook derived class. It will override the check_out() method of its Book base class. However, it will reuse the inherited Book class data members and member functions to manage its shelf location, author's name, and title." Figure 5.1 pictures a portion of a possible library lending material class hierarchy. The root of the class hierarchy is an abstract base class, LibMat. LibMat defines all the operations that are common to all the different types of library lending materials: check_in(), check_out(), due_date(), fine(), location(), and so on. LibMat does not represent an actual lending material object; rather, it is an artifact of our design. In fact, it is the key artifact. We call it an abstract base class. Figure 5.1. Portion of Library Lending Material Class Hierarchy
In an object-oriented program, we indirectly manipulate the class objects of our application through a pointer or reference of an abstract base class rather than directly manipulate the actual derived class objects of our application. This allows us to add or remove a derived class without the need for any modification to our existing program. For example, consider the following small function: void loan_check_in( LibMat &mat ) { // mat actually refers to a derived class object // such as Book, RentalBook, Magazines, and so on ... mat.check_in(); if ( mat.is_late() ) mat.assess_fine(); if ( mat.waiting_list(); mat.notify_available(); } There are no LibMat objects in our application, only Book, RentalBook, AudioCDs, and so on. How does this function actually work? What happens, for example, when the check_in() operation is invoked through mat? For this function to make sense, mat must somehow refer to one of the actual class objects of our application each time loan_check_in() is executed. In addition, the check_in() member function that is invoked must somehow resolve to the check_in() instance of the actual class object mat refers to. This is what happens. The question is, how does it work? The second unique aspect of object-oriented programming is polymorphism: the ability of a base class pointer or reference to refer transparently to any of its derived class objects. In our loan_check_in() function, for example, mat always addresses some object of one of the classes derived from LibMat. Which one? That cannot be determined until the actual execution of the program, and it is likely to vary with each invocation of loan_check_in(). Dynamic binding is a third aspect of object-oriented programming. In non-object-oriented programming, when we write mat.check_in(); the instance of check-in() to be executed is determined at compile-time based on mat's class type. Because the function to invoke is resolved before the program begins running, this is called static binding. In object-oriented programming, the compiler cannot know which instance of check_in() to invoke. This can be determined only during program execution based on the actual derived class object mat addresses each time loan_check_in() is invoked. The actual resolution of which derived class instance of check_in() to invoke each time must be delayed until run-time. This is what we mean by dynamic binding. Inheritance allows us to define families of classes that share a common interface, such as our library lending materials. Polymorphism allows us manipulate objects of these classes in a type-independent manner. We program the common interface through a pointer or reference of an abstract base class. The actual operation to invoke is not determined until run-time based on the type of the object actually addressed. ( Yes, polymorphism and dynamic binding are supported only when we are using a pointer or reference. I say more about that later.) If the library decides no longer to loan interactive books, we simply remove that class from the inheritance hierarchy. The implementation of loan_check_in() need not change. Similarly, if the library decides to charge a rental fee for certain audio books, we can implement a derived AudioRentalBook. loan_check_in() still does not need to change. If the library decides to loan laptop computers or video game equipment and cartridges, our inheritance hierarchy can accommodate that. |
I l@ve RuBoard |
![]() ![]() |