Inheritance

Document Sample
Inheritance Powered By Docstoc
					                                                   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++:
 Encapsulate code within functions
 Build classes
 Store classes and functions in separately-compiled libraries

 Convert functions into type-parameterized function templates

 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 — 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(),    ...
                       myTop,   ...
                  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 RevStack class that
    contains a Stack as a data member.

                        RevStack
                        class
                        new operations
                       including revised
                        push(), pop(), ...

                          Stack stObj
                       push(), pop(), ...

                       myTop,   ...
                      new data members


    Better, but:
      A RevStack is not a Stack; it has a Stack.

                                                           3
#3: Copy-&-paste approach: Build a new RevStack class,
    copying and pasting the data members and function
    members of Stack into RevStack .

             Stack class                RevStack
            push(), pop(),     ...       class
                                        new operations
                myTop,   ...           push(), pop(),   ...
                                       myTop,   ...
                                      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
       RevStack.                                                  4
#4: Object-oriented approach:
 Derive a new class RevStack 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
      RevStack class.
 (iii) Mistakes made in building RevStack 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 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-
                                OOP languages have three
oriented programming language. done this
                          We've
important properties:
      Inheritance
      Polymorphism, with the related concept of
       Encapsulation
                  dynamic or late binding


                                                                   6
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 myNumber;
       string myLastName,
              myFirstName;
       char myMiddleInitial;
       int myAge;
       Date myBirthDay; // Date is a user-defined type
       ...
   };
                                                                  7
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 HuntingLicense
 class DriversLicense
 {                                   {
   public:                            public:
    ...                                 ...
   private:                           private:
     License common;                     License common;
     int myVehicleType;                  string thePrey;
     string myRestrictionsCode;          Date seasonBegin,
   ...                                        seasonEnd;
 };                                   ...
                                     };
   class PetLicense
   {
     public:                      This has-a relation (inclusion)
      ...
     private:
                                  defines containment; i.e., when one
       License common;            object contains an instance of
       string myAnimalType;       another object.
      ...
   };

                                                                        8
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:
     DriversLicense 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 DriversLicense is a License,
            not a DriversLicense 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 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 myNumber;
                       string myLastName,
                               myFirstName;
                       char myMiddleInitial;
                       int myAge;
                        Date myBirthDay;
                       ...
                     };                                          11
Now we can derive classes for the more specialized licenses from
License:
    class DriversLicense : public License
    {
     public:
       ...
     protected:
       int myVehicleType;
       string myRestrictionsCode;
       ...
    };
         class HuntingLicense : public License
         {
           public:
            ...
           protected:
            string thePrey;    class PetLicense : public License
            Date seasonBegin,  {
                 seasonEnd;      public:
            ...                   ...
         };                      protected:
                                  string myAnimalType;
                                  ...
                               };

                                                                   12
Classes like DriversLicense, HuntingLicense, and
PetLicense 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 MooseLicense : public HuntingLicense
        {
        public:
           ...
        protected:
           int theAntlerMaximum;
           int theBullwinkleFactor;
           ...
        };

                                                                       13
  This leads to class hierarchies — usually pictured as a tree
  but with arrows drawn from a derived class to its base class:

                                     License



         Drivers                  Hunting             ...           Pet
         Licens e                 Licens e                        Licens e

             ...                     ...                               ...

    Car            Unicycle   Moose        Dinosaur          Dog             Hamster
  Licens e         Licens e   Licens e     Licens e         Licens e         Licens e


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

DerivedClassName : public BaseClassName
{
   ...
  // 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
 (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     public and protected, respectively
      private    private
      protected  protected


                                                                16
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 HuntingLicense is a License
                  A MooseLicense is a HuntingLicense
                  A MooseLicense is a License
                                                                       17
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 BusDriver : public License
      { ... }

Rather, we should use:
    class BusDriver
    {
        ...
      private:
        License myLicense;// 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 another class.
For example, a Fee() member function in a LicensePlate
class might have a parameter of type DriversLicense.
But this class simply uses the DriversLicense class — it
is not a DriversLicense and it does not have a
DriversLicense.


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 in a problem from the bottom up.
  2. Where commonality exists between objects, group the
common
     attributes into a base class:

         Attributes                       Attributes
         Common                            Common
        to Object 1                       to Object j
       thru Object i                     thru Object n
                               ...
              ...                               ...
   Object 1         Object i         Object j         Object n

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


                 Attributes Common
              to Object 1 thru Object n



    Attributes                        Attributes
     Common              ...           Common
    to Object 1                       to Object j
   thru Object i                     thru Object n


                                                         21
Once no more commonality exists, OO implementation then:
4. Proceeds from the top down, building the most general base
class(es):
                     Attributes Common
                  to Object 1 thru Object n

5. The less-general classes are then derived (publicly) from that
   base class(es):
                        Attributes Common
                     to Object 1 thru Object n



           Attributes                         Attributes
            Common              ...            Common
           to Object 1                        to Object j
          thru Object i                      thru Object n          22
6. Derivations continue until classes for the actual objects in the
   system are built:
                          Attributes Common
                       to Obje ct 1 thru Object n



            Attributes                         Attributes
             Common                 ...         Common
            to Object 1                        to Object j
           thru Object i                      thru Object n

                 ...                                 ...
      Object 1           Object i         Object j         Object n

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 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:
      // ... various Employee operations ...

   protected:
      long myIdNum;               // Employee's id number
      string myLastName,          //     "      last name
              myFirstName;        //     "      first name
      char myMiddleInitial;       //     "      middle initial
      int myDeptCode;             //     "      department code

        // ... other members common to all Employees
   };




                                                                  25
4. From the base class, derive classes containing special
   attributes: employee class:
   a. A salaried
      class SalariedEmployee : public Employee
      {
      public:
        // ... salaried employee operations ...

      protected:
         double mySalary;
      };

  b. An hourly employee class:
      class HourlyEmployee : public Employee
      {
      public:
        // ... hourly employee operations ...

      protected:
         double myWeeklyWage,
                myHoursWorked,
                myOverTimeFactor;
                                                            26
      };
  and others . . .


                                         Employee




           Salaried                 Hourly          Commissione        Contract
          Employee                 Employee             d              Employee
                                                     Employee




               Pro-      Consul-    Secre-    Tech
    Manager                                   Support   Sales     Reviewer   Editor
               grammer   tant       tary




Execu-   Super-
tive     visor

                                                                                      27
All of the classes that have Employee as an ancestor inherit the
members (data and function) of Employee.
For example, each HourlyEmployee and Manager object is
an Employee object so each contains the members myIdNum,
myLastName, myFirstName, and so on...

So, if Employee has a public operation to extract myIdNum,
  long IdNumber() const { return myIdNum; }
then it can be used by hourly employees and managers:
  HourlyEmployee hourlyEmp;
  Manager managerEmp;
      ...
  cout << hourlyEmp.IdNumber() << endl
       << managerEmp.IdNumber() << endl;
                                                             28
Reusability:
Suppose we define an output function member in Employee:
  void Employee::Print(ostream & out) const
  {
    out << myIdNum << ' ' << myLastName << ", "
        << myFirstName << ' ' << myMiddleNInitial << " "
        << myDeptCode << 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 SalariedEmployee::Print(ostream & out) const
 {
   Employee::Print(out);             //inherited members
   out << "$" << mySalary << endl;   //local members
 }

 void HourlyEmployee::Print(ostream & out) const
 {
   Employee::Print(out);              //inherited members
   out << "$" << myWeeklyWage << endl //local members
       << myHoursWorked << endl
       << myOverTimeFactor << endl;
 }



                                                       30
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
  class Address
  {
  public:
    string street,
           city, state,
           zip;
  }
and use it to declare some data member in the class
Employee.
Then, for example, a SalariedEmployee is-an Employee
                                                            31
and has-an Address.
Constructors and Inheritance

Consider Employee's constructor
   // Explicit-Value Constructor
   inline Employee::Employee(long id, string last,
                             string first, char initial,
                             int dept)
   {
     myIdNum = id;
     myLastName = last;
     myFirstName = first;
     myMiddleInitial = initial;
     myDeptCode = 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 SalariedEmployee explicit-value constructor
inline SalariedEmployee::SalariedEmployee
                    (long id, string last, string first,
                     char initial, int dept, double sal)
: Employee(id, last, first, initial, dept)
{
    mySalary = 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.
 SalariedEmployee()then initializes only its own (local)
 member(s).


                                                                33
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(ParameterList) : Base(ArgList)
{
  // 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 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 SalariedEmployee::SalariedEmployee
                    (long id, string last, string first,
                     char initial, int dept, double sal)
: Employee(id, last, first, initial, dept) , mySalary(sal)
{
}

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 = "", 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
SalariedEmployee and HourlyEmployee as described earlier.
The statements:
 Employee emp(11111, "Doe", "John", 'J', 11);
 SalariedEmployee empSal(22222, "Smith", "Mary", 'M', 22, 59900);
 HourlyEmployee empHr(33333, "Jones", "Jay", 'J', 33, 15.25, 40);
 cout << emp << endl << empSal << endl << empHr << endl;

then produce as output:
                               not:
 11111 Doe, John J    11       11111 Doe, John J    11

 22222 Smith, Mary M      22   22222 Smith, Mary M   22
                               $59900
 33333 Jones, Jay J    33
                               33333 Jones, Jay J    33
                               $15.25
                               40
                               1.5
                                                              37
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
 SalariedEmployee::Print() for SalariedEmployees
 HourlyEmployee::Print() for HourlyEmployees
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 a declaration
   Employee * eptr;
Since a SalariedEmployee is-an Employee, eptr can point to a
SalariedEmployee object:
   eptr = new SalariedEmployee;
Similarly, it can point to an HourlyEmployee object:
   eptr = new HourlyEmployee;
For the call
   eptr->Print(cout);
to work, SalariedEmployee::Print(cout) must be used when
eptr points to a SalariedEmployee, but HourlyEmployee::
Print(cout) must be used when eptr points to HourlyEmployee.
   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 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 PrototypeOfFunction = 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 LinkedList of Employee objects:
      LinkedList<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:

  L
       n         emp1          emp2              emp_n
                                           ...


 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 LinkedList of Employee pointers:
      LinkedList<Employee *> L;
Then each node of L can store a pointer to any object derived
from class Employee:        HourlyEmployee

               Employee                        SalariedEmployee

 L
      n         emp1           emp2              emp_n



                                         ...


Thus, salaried and hourly employees can be intermixed in
the same list, and we have a heterogeneous storage
                                                                  43
structure.
Now consider:
  Node * nPtr = L.first;
   while (nPtr != 0)
   {
      nptr->data->Print(cout);
      nptr = nPtr->next;
   }
For the call
   nPtr->data->Print(cout);
to work when nPtr->data points to a SalariedEmployee object,
    SalariedEmployee::Print() within that object must be
called;
but when nPtr->data is a pointer to an HourlyEmployee,
   HourlyEmployee::Print() within that object must be called.

Here is another instance where Print() must be a virtual function. 44

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:15
posted:11/9/2011
language:Estonian
pages:44