Read Chap 12 9 OOP ADTs Introduction to

  • Slides: 44
Download presentation
Read Chap. 12 9. OOP & ADTs: Introduction to Inheritance A. Inheritance, OOD, and

Read Chap. 12 9. OOP & ADTs: Introduction to Inheritance A. Inheritance, OOD, and OOP (§ 12. 1 & 12. 2) A major objective of OOP: writing reusable code (to avoid re-inventing the wheel). Ways to do this in C++: u Encapsulate code within functions u Build classes u Store classes and functions in separately-compiled libraries u Convert functions into type-parameterized function templates u Convert classes into type-parameterized class templates An additional approach that distinguishes OOP from the others: ® Inheritance: Define one class (derived class) in terms of another (base) class, reusing the data members and function members of the base class. 1

Example: Suppose a problem requires stack operations not provided in our stack class —

Example: Suppose a problem requires stack operations not provided in our stack class — e. g. , max(), min() Ways to Approach this: #1: Add function members to the Stack class that implement the new operations. Stack class new operations push(), pop(), my. Top, . . . new data members Bad: This can easily mess up a tested, operational class, creating problems for other client programs. 2

#2: An adapter approach: Build a new Rev. Stack class that contains a Stack

#2: An adapter approach: Build a new Rev. Stack class that contains a Stack as a data member. Rev. Stack class new operations including revised push(), pop(), . . . Stack st. Obj push(), pop(), . . . my. Top, . . . new data members Better, but: A Rev. Stack is not a Stack; it has a Stack. 3

#3: Copy-&-paste approach: Build a new Rev. Stack class, copying and pasting the data

#3: Copy-&-paste approach: Build a new Rev. Stack class, copying and pasting the data members and function members of Stack into Rev. Stack class push(), pop(), my. Top, . . . new operations cop y push(), pop(), my. Top, . . . new data members Almost right, but: These are separate independent classes. Modifying Stack (e. g. , changing from an array to a linked list for the stack elements) doesn't automatically update a Rev. Stack. 4

#4: Object-oriented approach: Derive a new class Rev. Stack from the Stack class, which

#4: Object-oriented approach: Derive a new class Rev. Stack from the Stack class, which is called its parent class or base class. This is the best: (i) A derived class inherits all members of its parent class (including its operations); we need not reinvent the wheel. (ii) Modifying the Stack class automatically updates the Rev. Stack class. (iii) Mistakes made in building Rev. Stack class will be local to it; the original Stack class remains unchanged and client programs are not affected. 5

Object-oriented design (OOD) is to engineer one’s software as follows: 1. Identify the objects

Object-oriented design (OOD) is to engineer one’s software as follows: 1. Identify the objects in the problem 2. Look for commonality in those objects 3. Define base classes containing that commonality 4. Define derived classes that inherit from the base class These last two steps are the most difficult aspects of OOD. Object-oriented programming (OOP) was first used to describe the programming environment for Smalltalk, the earliest true object-oriented programming language. OOP languages have three important properties: We've done this Encapsulation Inheritance Polymorphism, with the related concept of dynamic or late binding 6

B. Derived Classes Problem: Model various kinds of licenses. Old Approach: Build separate classes

B. Derived Classes Problem: Model various kinds of licenses. Old Approach: Build separate classes for each license from scratch OOD: What attributes do all licenses have in common? Then store these common attributes in a general (base) class License: class License { public: // Function members Display(), Read(), . . . private: // we'll change this in a minute long my. Number; string my. Last. Name, my. First. Name; char my. Middle. Initial; int my. Age; Date my. Birth. Day; // Date is a user-defined type. . . }; 7

We could include a data member of type License in each of the classes

We could include a data member of type License in each of the classes for the various kinds of licenses and then add new members: class Drivers. License { public: . . . private: License common; int my. Vehicle. Type; string my. Restrictions. Code; . . . }; class Pet. License { public: . . . private: License common; string my. Animal. Type; . . . }; class Hunting. License { public: . . . private: License common; string the. Prey; Date season. Begin, season. End; . . . }; This has-a relation (inclusion) defines containment; i. e. , when one object contains an instance of another object. 8

This inclusion technique works but it is a bit "clunky" and inefficient; for example,

This inclusion technique works but it is a bit "clunky" and inefficient; for example, we need to "double dot" to access members of the included object: Drivers. License h; . . . h. common. Display(cout); Worse. . . Can one say that a driver’s license is a license? No! This is bad OOD. Design should reflect reality not implementation. What we really want is the is-a relationship because a driver’s license is a license 9

So we need: a Drivers. License is a License, not a Drivers. License has

So we need: a Drivers. License is a License, not a Drivers. License has a License. we need inheritance. We will derive the specialized license classes from the base class License and add new members to store and operate on their new attributes. Problem: Private class members cannot be accessed outside of their class (except by friend functions), not even within derived classes. 10

One C++ solution: Members declared to be protected: can be accessed within a derived

One C++ solution: Members declared to be protected: can be accessed within a derived class, but remain inaccessible to programs or non-derived classes that use the class (except for friend functions). So change the private section in class License to a protected section: class License { public: // Function members // Display(), Read(), . . . protected: long my. Number; string my. Last. Name, my. First. Name; char my. Middle. Initial; int my. Age; Date my. Birth. Day; . . . }; 11

Now we can derive classes for the more specialized licenses from License: class Drivers.

Now we can derive classes for the more specialized licenses from License: class Drivers. License : public License { public: . . . protected: int my. Vehicle. Type; string my. Restrictions. Code; . . . }; class Hunting. License : public License { public: . . . protected: string the. Prey; class Pet. License : public License Date season. Begin, { season. End; public: . . . }; protected: string my. Animal. Type; . . . }; 12

Classes like Drivers. License, Hunting. License, and Pet. License are said to be derived

Classes like Drivers. License, Hunting. License, and Pet. License are said to be derived classes (or child classes or subclasses), and the class License from which they are derived is called a base class (or parent class or superclass). We have used protected sections rather than private ones in these derived classes to make it possible to derive "second-level" classes from these if/when it becomes necessary; for example: class Moose. License : public Hunting. License { public: . . . protected: int the. Antler. Maximum; int the. Bullwinkle. Factor; . . . }; 13

This leads to class hierarchies — usually pictured as a tree but with arrows

This leads to class hierarchies — usually pictured as a tree but with arrows drawn from a derived class to its base class: Each non-root class inherits the members of its ancestor classes. This means that an attribute needs to be defined only once (at the appropriate level), allowing a programmer to reuse the (one) definition many times. Java API 14

Usual Form of Declaration of a Derived Class Derived. Class. Name : public Base.

Usual Form of Declaration of a Derived Class Derived. Class. Name : public Base. Class. Name {. . . // new data members and // functions for derived class. . . } (More generally, the keyword public can be replaced by private or protected. ) 15

The Fundamental Property of Derived Classes They inherit the members of the base class

The Fundamental Property of Derived Classes They inherit the members of the base class (and thus the members of all ancestor classes). Other Properties: They cannot access private members of the base class. Access to public and protected members of the base class depends on the kind of inheritance specified in the heading: public and protected, respectively private protected 16

The most common is public inheritance; this is the only kind we will use.

The most common is public inheritance; this is the only kind we will use. It means that: We can use the public and protected members of the base class in a derived class just as though they were declared in the derived class itself. It gives rise to the is-a relationship: For class Derived : public Base { //. . . members of Derived. . . }; every Derived object is a Base object. For example: A Hunting. License is a License A Moose. License is a Hunting. License A Moose. License is a License 17

The property that derived classes inherit the members of ancestor classes can easily be

The property that derived classes inherit the members of ancestor classes can easily be misused. For example, it is bad design to do the following just to get the members of one class into another: class Bus. Driver : public License {. . . } Rather, we should use: class Bus. Driver {. . . private: License my. License; // a bus driver has a license. . . }; Design Principle: Don't use public inheritance for the has-a relationship. 18

A third relationship between classes is the uses relationship: One class might simply use

A third relationship between classes is the uses relationship: One class might simply use another class. For example, a Fee() member function in a License. Plate class might have a parameter of type Drivers. License. But this class simply uses the Drivers. License class — it is not a Drivers. License and it does not have a Drivers. License. It isn't always easy to tell which is the appropriate one to use. Two useful tests in deciding whether to derive Y from X: 1. Do the operations in X behave properly in Y? 2. (The "need-a use-a" test): If all you need is a Y, can you use an X? 19

Summary: The OOP approach to system design is to: 1. Carefully analyze the objects

Summary: The OOP approach to system design is to: 1. Carefully analyze the objects in a problem from the bottom up. 2. Where commonality exists between objects, group the common attributes into a base class: 20

3. Then repeat this approach “upwards” as appropriate: 21

3. Then repeat this approach “upwards” as appropriate: 21

Once no more commonality exists, OO implementation then: 4. Proceeds from the top down,

Once no more commonality exists, OO implementation then: 4. Proceeds from the top down, building the most general base class(es): 5. The less-general classes are then derived (publicly) from that base class(es): 22

6. Derivations continue until classes for the actual objects in the system are built:

6. Derivations continue until classes for the actual objects in the system are built: 7. These classes can then be used to construct the system’s objects. 23

C. Another (Classic) Example: Employees Problem: Design a payroll system. Following the four OOD

C. Another (Classic) Example: Employees Problem: Design a payroll system. Following the four OOD steps, we proceed as follows: 1. Identify the objects in the problem: Salaried employees Hourly employees 2. Look for commonality in those objects: what attributes do they share? Id number Name Department. . . 24

3. Define a base class containing the common data members: class Employee { public:

3. Define a base class containing the common data members: class Employee { public: //. . . various Employee operations. . . protected: long my. Id. Num; string my. Last. Name, my. First. Name; char my. Middle. Initial; int my. Dept. Code; // Employee's id number // " last name // " first name // " middle initial // " department code //. . . other members common to all Employees }; 25

4. From the base class, derive classes containing special attributes: a. A salaried employee

4. From the base class, derive classes containing special attributes: a. A salaried employee class: class Salaried. Employee : public Employee { public: //. . . salaried employee operations. . . protected: double my. Salary; }; b. An hourly employee class: class Hourly. Employee : public Employee { public: //. . . hourly employee operations. . . protected: double my. Weekly. Wage, my. Hours. Worked, my. Over. Time. Factor; }; 26

and others. . . Employee Hourly Employee Salaried Employee Manager Executive Programmer Consultant Secretary

and others. . . Employee Hourly Employee Salaried Employee Manager Executive Programmer Consultant Secretary Commissioned Employee Tech Support Sales Contract Employee Reviewer Editor Supervisor 27

All of the classes that have Employee as an ancestor inherit the members (data

All of the classes that have Employee as an ancestor inherit the members (data and function) of Employee. For example, each Hourly. Employee and Manager object is an Employee object so each contains the members my. Id. Num, my. Last. Name, my. First. Name, and so on. . . So, if Employee has a public operation to extract my. Id. Num, long Id. Number() const { return my. Id. Num; } then it can be used by hourly employees and managers: Hourly. Employee hourly. Emp; Manager manager. Emp; . . . cout << hourly. Emp. Id. Number() << endl << manager. Emp. Id. Number() << endl; 28

Reusability: Suppose we define an output function member in Employee: void Employee: : Print(ostream

Reusability: Suppose we define an output function member in Employee: void Employee: : Print(ostream & out) const { out << my. Id. Num << ' ' << my. Last. Name << ", " << my. First. Name << ' ' << my. Middle. NInitial << " " << my. Dept. Code << endl; Not } "overload" In derived classes, we can override Print() with new definitions that reuse the Print() function of class Employee. A class Deriv derived from class Base can call Base: : F() to reuse the work of the member function F() from the base class. 29

For example: void Salaried. Employee: : Print(ostream & out) const { Employee: : Print(out);

For example: void Salaried. Employee: : Print(ostream & out) const { Employee: : Print(out); //inherited members out << "$" << my. Salary << endl; //local members } void Hourly. Employee: : Print(ostream & out) const { Employee: : Print(out); //inherited members out << "$" << my. Weekly. Wage << endl //local members << my. Hours. Worked << endl << my. Over. Time. Factor << endl; } 30

Is-a and Has-a Relationships: Skip? An object can participate in is-a and has-a relationships

Is-a and Has-a Relationships: Skip? An object can participate in is-a and has-a relationships simultaneously. For example, we could define an address class Address { public: string street, city, state, zip; } and use it to declare some data member in the class Employee. Then, for example, a Salaried. Employee is-an Employee and has-an Address. 31

Constructors and Inheritance Consider Employee's constructor // Explicit-Value Constructor inline Employee: : Employee(long id,

Constructors and Inheritance Consider Employee's constructor // Explicit-Value Constructor inline Employee: : Employee(long id, string last, string first, char initial, int dept) { my. Id. Num = id; my. Last. Name = last; my. First. Name = first; my. Middle. Initial = initial; my. Dept. Code = dept; } A derived class can use a member-initialization-list to call the base-class constructor to initialize the inherited data members — easier than writing it from scratch. 32

// Definition of Salaried. Employee explicit-value constructor inline Salaried. Employee: : Salaried. Employee (long

// Definition of Salaried. Employee explicit-value constructor inline Salaried. Employee: : Salaried. Employee (long id, string last, string first, char initial, int dept, double sal) : Employee(id, last, first, initial, dept) { my. Salary = sal; } From the derived class constructor, pass the id number, last name, first name, middle initial and dept. code to the Employee constructor to initialize those members. Salaried. Employee()then initializes only its own (local) member(s). 33

General Principle of Derived Class Initialization Use the base class constructor to initialize base

General Principle of Derived Class Initialization Use the base class constructor to initialize base class members, and use the derived class constructor to initialize derived class members. General form of Member-Initialization-List Mechanism: Derive: : Derive(Parameter. List) : Base(Arg. List) { // initialize the non-inherited members // in the usual manner. . . } Initializations in a member-initialization-list are done first, before those in the body of the constructor function. 34

Member-initialization list can also be used to initialize new data members in the derived

Member-initialization list can also be used to initialize new data members in the derived class: Data member datamem of a derived class can be initialized to an initial value initval using the unusual function notation datamem(initval) in the member-initialization list. Example: inline Salaried. Employee: : Salaried. Employee (long id, string last, string first, , my. Salary(sal) char initial, int dept, double sal) : Employee(id, last, first, initial, dept) { } This is less commonly used than "normal" initialization datamem = initval; in the function body. It is used when initialization is desired before the constructor fires. 35

D. Polymorphism Consider: class Employee { public: Employee(long id = 0, string last =

D. Polymorphism Consider: class Employee { public: Employee(long id = 0, string last = "", string first = "", char initial = ' ', int dept = 0); void Print(ostream & out) const; //. . . other Employee operations. . . protected: //. . . data members common to all Employees }; // Definition of Print void Employee: : Print(ostream & out) const {. . . } // Definition of output operator<< ostream & operator<<(ostream & out, const Employee & emp) { emp. Print(out); return out; } 36

And suppose we have overridden Print() for the subclasses Salaried. Employee and Hourly. Employee

And suppose we have overridden Print() for the subclasses Salaried. Employee and Hourly. Employee as described earlier. The statements: Employee emp(11111, "Doe", "John", 'J', 11); Salaried. Employee emp. Sal(22222, "Smith", "Mary", 'M', 22, 59900); Hourly. Employee emp. Hr(33333, "Jones", "Jay", 'J', 33, 15. 25, 40); cout << emp << endl << emp. Sal << endl << emp. Hr << endl; not: then produce as output: 11111 Doe, John J 22222 Smith, Mary M 33333 Jones, Jay J 11111 Doe, John J 11 22 33 22222 Smith, Mary M $59900 11 33333 Jones, Jay J $15. 25 40 1. 5 22 33 37

This is because the call to Print() in the definition of operator<<() uses Employee's

This is because the call to Print() in the definition of operator<<() uses Employee's Print(). What we need is dynamic or late binding: Don't bind a definition of a function member to a call to that function until runtime. This is accomplished by declaring Print() to be a virtual function by prepending the keyword virtual to its prototype in the Employee class: class Employee { public: //. . . virtual void Print(ostream & out) const; //. . . other Employee operations. . . private: //. . . data members common to all Employees }; 38

This works; operator<<() will use Employee: : Print() for Employees Salaried. Employee: : Print()

This works; operator<<() will use Employee: : Print() for Employees Salaried. Employee: : Print() for Salaried. Employees Hourly. Employee: : Print() for Hourly. Employees The same function call can cause different effects at different times — has many forms — based on the function to which the call is bound. Such calls are described as polymorphic (Greek for "many forms"). Polymorphism is another important advantage of inheritance in OOP languages. Thanks to polymorphism, we can apply operator<<() to derived class objects without explicitly overloading it for those objects. Note: This is a good reason to define operator<<() so that it calls some output function member of a class rather than making it a friend function. 39

Another Example: A base-class pointer can point to any derived class object. So consider

Another Example: A base-class pointer can point to any derived class object. So consider a declaration Employee * eptr; Since a Salaried. Employee is-an Employee, eptr can point to a Salaried. Employee object: eptr = new Salaried. Employee; Similarly, it can point to an Hourly. Employee object: eptr = new Hourly. Employee; For the call eptr->Print(cout); to work, Salaried. Employee: : Print(cout) must be used when eptr points to a Salaried. Employee, but Hourly. Employee: : Print(cout) must be used when eptr points to Hourly. Employee. Here is another instance where Print() must be a virtual function so that this function call can be bound to different function definitions at different times. 40

By declaring a base-class function member to be virtual, a derived class can override

By declaring a base-class function member to be virtual, a derived class can override that function so that calls to it though a pointer or reference will be bound (at run-time) to the appropriate definition. If we want to force derived classes to provide definitions of some virtual function, we make it a pure virtual function — also called an abstract function — and the class is called an abstract class. This is accomplished in C++ by attaching = 0 to the function's prototype: virtual Prototype. Of. Function = 0; No definition of the function is given in the base class. Classes derived from it must provide a definition. 41

E. Hetergeneous Data Structures Consider a Linked. List of Employee objects: Linked. List<Employee> L;

E. Hetergeneous Data Structures Consider a Linked. List of Employee objects: Linked. List<Employee> L; Each node of L will only have space for an Employee, with no space for the additional data of an hourly or salaried employee: Such a list is a homogeneous structure: Each value in the list must be of the same type (Employee). 42

Now suppose we make L a Linked. List of Employee pointers: Linked. List<Employee *>

Now suppose we make L a Linked. List of Employee pointers: Linked. List<Employee *> L; Then each node of L can store a pointer to any object derived from class Hourly. Employee: Employee Salaried. Employee Thus, salaried and hourly employees can be intermixed in the same list, and we have a heterogeneous storage structure. 43

Now consider: Node * n. Ptr = L. first; while (n. Ptr != 0)

Now consider: Node * n. Ptr = L. first; while (n. Ptr != 0) { nptr->data->Print(cout); nptr = n. Ptr->next; } For the call n. Ptr->data->Print(cout); to work when n. Ptr->data points to a Salaried. Employee object, Salaried. Employee: : Print() within that object must be called; but when n. Ptr->data is a pointer to an Hourly. Employee, Hourly. Employee: : Print() within that object must be called. Here is another instance where Print() must be a virtual function. 44