10.2. Inheritance

Time: 00:04:41 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)

Inheritance is a fundamental component of the object-oriented paradigm. Understandably, there are many different ways of defining and understanding it. The original UML authors, Booch, Rumbaugh, & Jacobson1, formally define inheritance as "The mechanism by which more-specific elements incorporate the structure and behavior of more-general elements" (p. 456). Stated simply, the top class in an inheritance hierarchy represents a general "thing," and lower classes are more specific. Less formally, the relationship means that given two classes related by inheritance, one class defines some members (variables and functions), and the other class inherits and can use them. All the different ways of defining or looking at inheritance follow from this mechanism.

Inheritance is a complex and abstract concept describing how two classes relate. We can make the idea a little more concrete with a graphical representation or picture of the related classes. We can also make it easier to discuss inheritance if we distinguish and label the various roles played by each class.

The picture represents two classes, A and B, as two rectangles connected by the UML inheritance symbol. A is the superclass, while B is the subclass. The picture also lists alternate name pairs for each class: Parent/Child, Superclass/subclass, Base class/Derived class, and Ancestor/Descendant.
Inheritance. UML class diagrams denote an inheritance relationship between two classes with an arrow with a outlined, three-sided head. Each class plays a specific role in the relationship, where the role names depend on the context. In the object-oriented vernacular, or the UML or Java languages, the roles are called "superclass" and "subclass." Formally, C++ calls the classes "base class" and "derived class," but experienced C++ programmers also use the other listed terms. The arrowhead always attaches to the superclass, and the undecorated end always attaches to the subclass. The subclass inherits all the superclass features (i.e., members).

Inheritance is an important and complex relationship, and many ways exist to define or understand it. The following figures explore some of the many aspects of this vital class relationship.

Generalization

What do Booch, Rumbaugh, and Jacobson mean when they speak of "more-specific elements" and "more-general elements?"

Illustrates an inheritance hierarchy with a superclass named Car with two subclasses named Sedan and Convertible. The inheritance symbol has one arrowhead, but the tail splits and connects to connect with each subclass. Illustrates an inheritance hierarchy with a superclass named Car with two subclasses named Sedan and Convertible. A separate inheritance arrow joins each subclass to the superclass.
Inheritance as generalization. In the context of object orientation, generalization is a synonym for inheritance. We can illustrate this with a more concrete example based on a Car and a Sedan. Inheritance is often called an is-a or an is-a kind-of relationship (read from the subclass to the superclass). For example, "a Sedan is-a Car" or "a Sedan is-a kind-of Car." In this example, generalization suggests that all Sedans are Cars, but not all Cars are Sedans - some are Convertibles. Finally, it doesn't sound right (at least in English) to say, "a Sedan has-a Car." Listening to how the description of a relationship sounds can help us choose the best relationship and to build it in the correct direction.

Inheritance is alternately known as generalization because classes appearing at or near the top of an inheritance hierarchy represent general classes. At the same time, those at or near the bottom are more specialized. So, a Car is general - there are many different kinds of Cars. Alternatively, Sedans and Convertibles are more specialized kinds of Cars. There is no syntactic limit to the number of subclasses a superclass may have: an inheritance hierarchy can be arbitrarily wide and tall.

The two inheritance diagrams are equivalent: the version on the left combines the arrowheads, which is useful when there are many subclasses, while the one on the right uses a separate arrowhead for each inheritance connector.

Inheriting Features

What do Booch, Rumbaugh, and Jacobson mean when they suggest one class can "incorporate the structure and behavior" of another? Or, as I suggested, that a class can use the features defined in another class?

A complex inheritance hierarchy where Account is the superclass and Loan is the subclass. Account has three private data members and two public member functions: string name, int accountID, double balance, double getBalance(), and void setBalance(double b). Loan inherits all five members from Account but has three private data members and one public member function of its own: double principle, double rate, int periods, and double payment().
double Loan::payment()
{
    double p = principle * rate / (1 - pow(1 + rate, -periods));
    setBalance(getBalance() - p + balance * rate);

    return p;
}

int main()
{
    Loan carLoan;

    cout << carLoan.getBalance() << endl;
    cout << carLoan.payment() << endl;
    
    return 0;
}
Inheriting functions and variables. A superclass's ability to share its features with its subclasses is one of the most important aspects of inheritance. If we were to write an object-oriented program to automate a bank or credit union, we might find it convenient to create a general Account class and several specialized kinds of accounts, such as Loan. An Account has a name, an account ID, and a balance. Through inheritance, a Loan is-a special kind of Account, and it inherits the name, account ID, and balance - it has and can use these members without redefining them.

The Loan payment function can call the setter and getter functions in the Account class, which in turn access the Account's private member variables. An instance of the Loan class (carLoan in this example) can call any public functions in the Account class (e.g., getBalance) in addition to any public functions defined in the Loan class (e.g., payment). In this sense, inheritance is a way to reuse code in a program. While we have used functions to reuse code in the past, inheritance gives us a way, at least to some extent, to also reuse variables.

Similarities Versus Differences

Before joining the UML team, Rumbaugh2 and his group at General Electric created the Object Modeling Technique (OMT), one of the languages unified into the UML. They defined inheritance as a descriptive abstraction "for sharing the similarities among classes while preserving their differences" (p. 38).

A Venn diagram consisting of two nested circles. The smaller inner circle represents the Account class, which has a name, an accountID, a balance, and the functions getBalance(), and setBalance(). The larger outer circle represents the Loan class, which has a principle, a rate, periods, and the payment() function. The nesting suggests that both classes have the members defined in the Account class but that the Loan's features are unique to that class.
Class similarities and differences. Classes can share some similarities while maintaining their differences. I think understanding the former is easier than the latter. For example, it makes sense that both a Person and a Student have a name, but while a Student naturally has a GPA, the average Person does not. Based on the Account and Loan inheritance diagram of the previous figure, a Venn diagram helps illustrate how inheritance represents class similarities and differences.

As a Loan is-an Account, it inherits all the members (variables and functions) defined in the Account class. So, an Account object and a Loan object are the same in the sense that a Loan has all of the variables and functions that an Account has. Still, they are different in the sense that a Loan object likely has members that an Account object does not have. In general, the members defined in the superclass are in common with all subclasses, while the members defined in a subclass are unique to that class.

Inheritance And Objects

The Venn diagram illustrating the similarities and differences between a super and subclass also suggests how the inheritance relationship is implemented, at least in part.

An object instantiated from a subclass, Loan in this example, has embedded or nested inside it an object instantiated from its superclass, Account in this example.
Instantiating a subclass: An abstract representation. Whenever a program instantiates a subclass, it simultaneously instantiates its superclass, and the compiler nests or embeds the superclass object inside the subclass object. When a program instantiates a subclass with multiple superclass levels (i.e., the inheritance hierarchy is three or more levels tall), it simultaneously instantiates all the superclasses, and the resulting objects are nested like Russian dolls. The compiler places each nested object at the top (i.e., at the starting address) of the surrounding object. The abstract illustration accurately represents the objects' organization in memory regarding the member variables, but the compiler accesses member functions differently to avoid duplicating machine code.

Substitutability

Fowler3 explains that "An important principle of using inheritance effectively is substitutability. I should be able to substitute a [subclass] within any code that requires a [superclass], and everything should work fine" (pp. 45-46). Si Alhir4 (1998) adds that while "a subclass instance may be substituted where a superclass instance is required... The reverse is not true, however: a superclass cannot be substituted for a subclass" (p. 61). Substitutability, and its directional nature, are a consequence of the similarities and differences between super- and subclasses: a subclass has everything that a superclass has but not the other way around. In a later chapter, we'll expand the concept of substitutability into the fundamental concept of polymorphism.

An inheritance hierarchy with a superclass named Shape and three subclasses named Circle, Rectangle, and Triangle.
void render(Shape* s) { . . . }

	. . .

Circle c;
render(&c);
Substitutability. We begin with a general Shape class with three more specialized subclasses: Circle, Rectangle, and Triangle. First, it makes sense to say that "a Circle is-a Shape," and similarly for Rectangle and Triangle. Now, suppose that some C++ code (the render function) requires a Shape object; substitutability implies that we may substitute a Circle, a Rectangle, or a Triangle in place of the Shape. (The illustrated syntax is an oversimplification that we'll refine in a subsequent chapter.) Substitutability is the starting point for polymorphism, the final requirement for full object orientation, and the topic of a subsequent chapter.

Inheritance Summary: Filling In The Table

Inheritance has some property values in common with other class relationships, but it has one unique, distinguishing characteristic: It is the only relationship forming an "is-a" binding between classes. The following figure summarizes the property values presented above. Use the summary to check and complete your entries in one of the blank Class Relationship Tables located at the end of the chapter.

  1. Semantics. Inheritance is the only is a relationship, so its semantics or meaning is the fundamental difference between inheritance and the other relationships. In terms of the inheritance hierarchy illustrated in Figure 6 above:
    • a Circle is-a Shape
    • a Rectangle is-a Shape
    • a Triangle is-a Shape
  2. Directionality. Inheritance is a unidirectional or a one-way relationship. Unidirectionality means that
    An inheritance hierarchy with a superclass named Parent and subclass named Child. An inheritance connector, with an outlined arrow head attached to the superclass and the plain end attached to the subclass, joins the two classes. An adjacent arrow points from Child to Parent, indicating that inheritance operations are unidirectional from the Child to the Parent. The same UML inheritance hierarchy, but the adjacent arrow indicates the Child can pass messages to the Parent. The same UML inheritance hierarchy, but the adjacent arrow indicates that the Child knows about the Parent but not the other way around. AThe same UML inheritance hierarchy, but the adjacent arrow indicates that a program can navigate from the Child to the Parent.
    (a)(b)(c)(d)
    1. the operations may only take place in one direction: from the child to the parent
    2. the subclass or child object can send a message to the superclass or parent object, which can respond to a child's messages but cannot initiate message sending
    3. the child "knows" about the parent, but the parent doesn't "know" about the child
    4. it is possible to navigate from the child to the parent object but not from the parent to the child
  3. Binding Strength. The binding between the two objects is very strong or tight because the superclass or parent object is embedded inside the subclass or child object (Figure 5). The strength of the bond implies the final two characteristics:
    • Lifetime. The two objects have a coincident lifetime, which means that the two objects are created and destroyed at the same time. It also means that the relationship between the two objects is permanent - it cannot be altered or broken.
    • Sharing. The binding between the two objects is so tight or strong that it forms an exclusive relationship - the subclass or child object does not share its superclass or parent object with any other object in the program.
  4. C++ implementation.
    class Circle : public Shape
    {
        . . .
    };
Inheritance property values. Inheritance is the only class relationship with a dedicated implementation syntax; the other relationships are implemented with variables. Inheritance is necessary for upcasting, function overriding, and polymorphism - all covered in the polymorphism chapter.