The Principle of Inheritance

 < Free Open Study > 



Classes tend to work together both logically and physically. The principle of inheritance helps promote code reuse and allows you to group classes into a relational hierarchy. For example, we might all agree that CEmployee and CSalesEmployee are somehow logically related in a system. Using C++, we can model this relationship physically by establishing base and derived class relationships (you may know them as parent/child or super/sub- class relationships). Classes may be related either by what is known as classical inheritance (Is-A) or the containment and delegation model (Has-A). In either light, inheritance always implies that one class is a specialized form of another.

The "Is-A" Relationship: Classical Inheritance

In classical inheritance, a subclass extends existing functionality from a superclass. Consider again our friend CEmployee. You have thoughtfully populated CEmployee's public interface with numerous methods, and preserved encapsulation with private data manipulated by public accessors and mutators. Now, imagine you wish to leverage CEmployee by deriving a new class called CSalesEmployee. CSalesEmployee is everything CEmployee is, plus a little bit more. You may wish to have the state of a CSalesEmployee object reflect the number of sales in a given week, a trait that a typical CEmployee object does not need to be concerned with. In effect, a CSalesEmployee "is a" type of CEmployee. The Is-A relationship can be diagrammed as so:

click to expand
Figure 1-12: Classical inheritance suggests an Is-A relationship.

We will be building a complete Employee hierarchy at the end of this chapter, but for now let's just add the following methods to the public sector of the CEmployee class, and establish an Is-A relationship with CSalesEmployee:

// The CEmployee base class. class CEmployee { public:      CEmployee ();      CEmployee (int ID, char* name, float startingSalary);      virtual ~CEmployee();      // These will be inherited by any sub class.      void DisplayClassType() {cout << "I am a CEmployee object." << endl; }      float GetPay() {return m_currPay/52;} protected:  // Grant access to sub classes, but not object user.      float        m_currPay;      int    m_ID;      char   m_name[MAX_LENGTH]; };

Here, GetPay() is the accessor for the private m_currPay data point, which is computed on a (roughly) weekly basis.

A Brief Word on RTTI

The DisplayClassType() function prints out the class name and is a simple diagnostic run-time type information (RTTI) method at this point. RTTI is a facility of many OO languages, which provides a way to discover the functionality of an object at run time. This can be a very powerful trick.

Imagine, for example, that we have a collection object maintaining a large number of related (but not identical) objects. If we have some way to dynamically discover which sort of object we have just pulled from the collection, we can make a set of appropriate requests from the object. Many C++ frameworks (such as MFC) provide a set of RTTI functions to make use of. As well, most C++ compilers also offer the typeid() function to support basic RTTI. The problem is that RTTI tends to be a very compiler-specific protocol. Here we have simply rolled our own.

COM also provides a form of RTTI through a standard COM interface named IUnknown. One of the methods of IUnknown, QueryInterface(), also provides a way to discover the functionality of an object at run time. We will get to know interfaces in the next chapter, and IUnknown in Chapter 3. As for DisplayClassType(), we will see a real application for this method in our upcoming lab, so bear with me for now.

Subclassing in C++: Leveraging Base Class Functionality

Next, assume we wish to create a new class that leverages the behavior of CEmployee. Using classical inheritance, CSalesEmployee will inherit any public or protected member defined in CEmployee. Recall the distinction between the public and protected keywords: If an object user creates a CSalesEmployee object, all public methods of both CEmployee and CSalesEmployee are directly available, whereas the protected data is not. However, as you develop the functionality of CSalesEmployee, you may refer to the inherited protected members provided by your base class.

Our sales employee class needs to hold the current number of weekly sales and calculate its pay accordingly. Using C++ syntax, we may model the Is-A relationship between CEmployee and CSalesEmployee as the following:

// Using classical inheritance to model the Is-A relationship. class CSalesEmployee: public CEmployee { public:      CSalesEmployee(int ID, char* name, float startingPay, int sales);      virtual ~ CSalesEmployee();      // Methods unique to CSalesEmployee.      int GetNumberOfSales() { return m_numberOfSales;}      int SetNumberOfSales(int numb) { m_numberOfSales = numb;} private:      // Unique instance data to the sales employee class.      int m_numberOfSales; };

To set the state of the derived CSalesEmployee object we have two syntactical options available to us. The first option is to simply assign the inherited protected data from CEmployee in the body of our constructor as so:

// Call is automatically made to the default constructor of CEmployee. CSalesEmployee::CSalesEmployee(int ID, char* name, float startingPay, int sales) {           m_ID = ID;                    // Inherited from CEmployee.      strcpy(m_name, name);         // Inherited from CEmployee.      m_currPay = startingPay;      // Inherited from CEmployee.      m_numberOfSales = sales;      // Our custom data. }

Doing so will trigger the default constructor of our base class (CEmployee) automatically. However, as our base class already has an overloaded constructor which knows how to assign the ID, name, and starting salary, we may use option two. Here we explicitly call a base class constructor and initialize the inherited data using the member initialization list:

// Explicitly call a base class constructor from the member initialization list. CSalesEmployee::CSalesEmployee(int ID, char* name, float startingPay, int sales)      : CEmployee(ID, name, startingPay) {      m_numberOfSales = sales;      // Now we can just worry about our unique data. }

All things being equal, both approaches are more or less equivalent, so take your pick. Now with the Is-A relationship defined, we can make use of the inherited functionality of a CEmployee object from a CSalesEmployee instance:

// Using base and derived classes. // void main(void) {      // Create a base class and derived class.      CEmployee joeBlow(20, "Joe", 45000);      CSalesEmployee slickRyan(30, "Ryan", 20000, 500);      // Ask each object to identify itself.      joeBlow.DisplayClassType();          // Prints "I am a CEmployee object"      slickRyan.DisplayClassType();     // Prints "I am a CEmployee object" (humm)       // GetPay() has been inherited from CEmployee,      // however it makes no use of the number of sales!      cout << slickRyan.GetPay();      // Returns m_numberOfSales      cout << slickRyan.GetNumberOfSales();      }

We have indeed inherited base class functionality; however, our CSalesEmployee objects are not working quite as expected. Each object instance identifies itself as a CEmployee type. Ideally each object in the inheritance chain should respond to the DisplayClassType() message in its own unique way.

In addition, the GetPay() method which we inherited from CEmployee does not make use of the number of sales (which it should, to allow for commission). We will fix these problems when we examine polymorphism later in this chapter. For now, consider them "features" of the system.

Examining the "Has-A" Relationship: Containment and Delegation

So then, classical inheritance allows us to leverage code by creating a dependency on some class higher up the chain. The other sort of inheritance found in many OOLs (and the only form of inheritance supported by COM) is the Has-A relationship, also known as the containment/delegation model. Here, reuse is achieved when an outer class creates and uses inner classes to reuse code and extend its own functionality. If the outer class wishes to expose the inner class's functionality to the object user, it extends its own public interface with methods that simply make calls (or delegates) to the inner object. The Has-A relationship is diagrammed in Figure 1-13:

click to expand
Figure 1-13: Containment of objects.

Note that users of outer objects (CSalesEmployee) typically have no knowledge that it maintains any inner objects (such as CEmployee) to help it get its work done. Why? Encapsulation, of course! The outer class is in charge of creating and exposing the inner class's functionality through its own public sector. If we wish to reuse the CEmployee class as an inner object, we may define the CSalesEmployee class as the following:

// CSalesEmployee using containment to reuse existing functionality. class CSalesEmployee { public:      CSalesEmployee();      CSalesEmployee(int ID, char* name, float startingPay, int sales);      float GetPay();      int GetNumberOfSales() { return m_numberOfSales;}      int SetNumberOfSales(int numb) { m_numberOfSales = numb;}      // We can now respond to this message our own way.      void DisplayClassType() {cout << "I am a CSalesEmployee object." << endl; } private:      CEmployee m_Emp;     // CSalesEmployee Has-A CEmployee .      int m_numberOfSales; };

The inner CEmployee object may be constructed using the member initialization list:

// The member initialization is a handy way to construct inner objects CSalesEmployee::CSalesEmployee(int ID, char* name, float startingPay, int sales)      : m_Emp(ID, name, startingPay)     // Inner objects created first. {      m_numberOfSales = sales;           // Assign sales data. }

The GetPay() method of the salesperson is implemented with help from the inner object, m_Emp. Again, the act of extending the containing class's public interface to indirectly use inner objects is called delegation:

// The outer class makes use of the inner class to calculate the pay of a // Sales employee. float CSalesEmployee::GetPay () {      // Delegate this call to the inner object to help get the real work done.      return (m_numberOfSales * 5) + ( m_Emp.GetPay() ); }

As shown previously, DisplayClassType() is defined inline and prints out a unique string without any help from CEmployee at all. By using containment and delegation, both CEmployee and CSalesEmployee behave as unique entities, responding correctly to GetPay() and DisplayClassType():

// Working with contained and stand-alone objects. void main(void) {      CEmployee joeBlow(20, "Joe", 45000);      CSalesEmployee slickRyan(30, "Ryan", 20000, 500);      // Ask each object to identify itself.      joeBlow.DisplayClassType();          // "I am a CEmployee object"      slickRyan.DisplayClassType();     // "I am a CSalesEmployee object"      // GetPay() now returns a value based on commissions.      cout << slickRyan.GetPay(); } 

To see how to repair the previous Is-A relationship to behave the same as our Has-A relationship, we need to provide a way for a derived class to override methods declared in a base class. This is a call for polymorphism, which is the final pillar of object-oriented technology.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net