Inheritance: The is-a relation

Steven Zeil

Last modified: Oct 25, 2018
Contents:

1 Generalization & Specialization

In an earlier lesson, we introduced the idea of a generalization/specialization relationship between classes.


Specialization Example

 

For example, a check and a deposit are actually specializations of the more general concept of a “transaction”.

If I were to assert, therefore, that every transaction has a date and an amount, we would understand that the same is true for Checks and Deposits.

Or, from the interface point of view, if I were to assert that every Transaction has a function member apply that takes a Balance as its only parameter, then Checks and Deposits must support the same operation.

1.1 Inheritance

In programming languages, generalization is denoted by inheritance and/or subtyping.

A class C inherits from class D if C has all the data members and messages of D.

D is called a base class of C.


Inheritance Example

 
This example suggests that Teachers and Students will inherit from University Personnel.


Inheritance Example

 


Multiple Inheritance

Inheriting from multiple base classes is called multiple inheritance.

It’s reasonably common in domain and analysis models, but designers often try to remove it before they get to the stage of coding. That’s because multiple inheritance can lead to complications.

It’s pretty obvious that the TA’s name is not changed depending on whether they are doing teacher stuff or student stuff at the time. But it’s not hard to imagine that some Universities might use distinct ranges of numbers for teachers than for students, requiring a TA to actually have two different numbers. And, how would you indicate your preference for inheriting one or two copies of a data member in a programming language.

It gets a bit messy, but it can be done in C++. On the other hand, Java disallows multiple inheritance as an unnecessary complication (partly because, as we will see, Java’s interface construct let’s us achieve much of the same flexibility with fewer complications.

1.2 Subtyping

A closely related idea:

D is called a superclass or supertype of C.


What’s the Difference?


Effects of Subtyping and Inheritance

applyToBal.cpp
void applyToCurrentBalance (CheckBook cbook, Transaction trans) {
   Balance b = cbook.getCurrentBalance();
   trans.apply (b);
   cbook.setCurrentBalance(b);
}
   ⋮
CheckBook myCheckBook;
Check check;
Transaction transaction;
Balance bal;
   ⋮
bal = myCheckBook.getCurrentBalance();
transaction.apply (bal);                         ➀
check.apply (bal);                               ➁
applyToCurrentBalance(myCheckBook, transaction); ➂
applyToCurrentBalance(myCheckBook, check);       ➃

 
For example, the last four statements in the code above are all legal, but the reasons vary:


C++ Combines Inheritance & Subtyping

In most OOPLs, including C++, inheritance and subtyping are combined.

That makes the distinction between inheritance and subtyping moot in C++.

The same does not hold, however, of Java, where only the first two of the four above statements are true.

2 Inheritance & Subtyping in C++

The construct

class C : public Super {

indicates that

2.1 Inheritance Example - Values in a Spreadsheet

cell.h
#ifndef CELL_H
#define CELL_H

#include "cellname.h"
#include "observable.h"
#include "observer.h"
#include "strvalue.h"

class Expression;
class Value;
class SpreadSheet;


// A single cell within a Spreadsheet
class Cell: public Observable, Observer
{
public:
  Cell (SpreadSheet& sheet, CellName name);
  Cell(const Cell&);

  ~Cell();

  CellName getName() const;

  const Expression* getFormula() const;
  void putFormula(Expression*);

  const Value* getValue() const;
  const Value* evaluateFormula();

  bool getValueIsCurrent() const;
  void putValueIsCurrent(bool);


  virtual void notify (Observable* changedCell);

private:
  SpreadSheet& theSheet;
  CellName theName;
  Expression* theFormula;
  Value* theValue;
  bool outOfDate;
  static StringValue defaultValue;
};

#endif

 
Every cell in the spreadsheet contains


Values

The interface for values is

value.h
#ifndef VALUE_H
#define VALUE_H

#include <string>
#include <typeinfo>

//
// Represents a value that might be obtained for some spreadsheet cell
// when its formula was evaluated.
// 
// Values may come in many forms. At the very least, we can expect that
// our spreadsheet will support numeric and string values, and will
// probably need an "error" or "invalid" value type as well. Later we may 
// want to add addiitonal value kinds, such as currency or dates.
//
class Value
{
public:
  virtual ~Value() {}


  virtual std::string render (unsigned maxWidth) const = 0;
  // Produce a string denoting this value such that the
  // string's length() <= maxWidth (assuming maxWidth > 0)
  // If maxWidth==0, then the output string may be arbitrarily long.
  // This function is intended to supply the text for display in the
  // cells of a spreadsheet.


  virtual Value* clone() const = 0;
  // make a copy of this value

protected:
  virtual bool isEqual (const Value& v) const = 0;
  //pre: typeid(*this) == typeid(v)
  //  Returns true iff this value is equal to v, using a comparison
  //  appropriate to the kind of value.

  friend bool operator== (const Value&, const Value&);
};

inline
bool operator== (const Value& left, const Value& right)
{
  return (typeid(left) == typeid(right))
    && left.isEqual(right);
}

#endif

Numeric Values

Numeric values hold numbers.

numvalue.h
#ifndef NUMVALUE_H
#define NUMVALUE_H

#include "value.h"

//
// Numeric values in the spreadsheet.
//
class NumericValue: public Value
{
  double d;

public:
  NumericValue():d(0.0)  {}
  NumericValue (double x): d(x) {}

  virtual std::string render (unsigned maxWidth) const;
  // Produce a string denoting this value such that the
  // string's length() <= maxWidth (assuming maxWidth > 0)
  // If maxWidth==0, then the output string may be arbitrarily long.
  // This function is intended to supply the text for display in the
  // cells of a spreadsheet.


  virtual Value* clone() const;

  double getNumericValue() const {return d;}

protected:
  virtual bool isEqual (const Value& v) const;
  //pre: typeid() == v.typeid()
  //  Returns true iff this value is equal to v, using a comparison
  //  appropriate to the kind of value.

};

#endif
 void foo (NumericValue nv) {
     cout << nv.render(8) << endl;
 }
 
void foo (Value v; NumericValue nv) {
    if (v == nv) {
       ⋮


String Values

String values hold numbers.

strvalue.h
#ifndef STRVALUE_H
#define STRVALUE_H

#include "value.h"

//
// String values in the spreadsheet.
//
class StringValue: public Value
{
  std::string s;
  static const char* theValueKindName;

public:
  StringValue()  {}
  StringValue (std::string x): s(x) {}


  virtual std::string render (unsigned maxWidth) const;
  // Produce a string denoting this value such that the
  // string's length() <= maxWidth (assuming maxWidth > 0)
  // If maxWidth==0, then the output string may be arbitrarily long.
  // This function is intended to supply the text for display in the
  // cells of a spreadsheet.


  std::string getStringValue() const {return s;}

  virtual Value* clone() const;



protected:
  virtual bool isEqual (const Value& v) const;
  //pre: valueKind() == v.valueKind()
  //  Returns true iff this value is equal to v, using a comparison
  //  appropriate to the kind of value.

};

#endif

Error Values

Error values store no data at all, but are used as placeholders in a cell whose calculations have failed for some reason.

errvalue.h
#ifndef ERRVALUE_H
#define ERRVALUE_H

#include "value.h"

//
// Erroneous/invalid values in the spreadsheet.
//
class ErrorValue: public Value
{
  static const char* theValueKindName;

public:
  ErrorValue()  {}

  virtual std::string render (unsigned maxWidth) const;
  // Produce a string denoting this value such that the
  // string's length() <= maxWidth (assuming maxWidth > 0)
  // If maxWidth==0, then the output string may be arbitrarily long.
  // This function is intended to supply the text for display in the
  // cells of a spreadsheet.


  virtual Value* clone() const;

protected:
  virtual bool isEqual (const Value& v) const;
  //pre: valueKind() == v.valueKind()
  //  Returns true iff this value is equal to v, using a comparison
  //  appropriate to the kind of value.

};

#endif

3 Inheritance in Java

Inheritance among classes in Java works almost identically to C++. The only difference is a rather minor change in syntax. Instead of this C++ syntax, for example,

class NumericValue: public Value {

Java uses the reserved word extends to indicate the same thing:

class NumericValue extends Value {

3.1 Shrubs and Trees

What is different in Java is not how we do inheritance, but how often.

This is not an empty piece of language design theory. As we will see shortly, there are very real functions declared by Object that we inherit and may want to use. In fact, we have seen some of them (e.g., equals, clone, and toString) in our earlier checklist. One reason I was able to assert that these members represent a consensus among Java programmers is simply because they are encoded into the Object.

Keep in mind that, even if we do explicitly inherit from some other base class, that base class will either inherit from Object or from some other class that either inherits from Object or … Eventually, we will be inheriting from Object, so the members declared there will be inherited by every class.

This special Object class contributes to a difference in style between C++ and Java.

C++ programs use inheritance only rarely and when there is an obvious need for it.

Spreadsheet inheritance in C++:

 

Our spreadsheet program has inheritance, but only for a minority of closely related classes.

This style permeates C++. The std:: library has very little use of inheritance, particularly in the design of utility data structures and collections.


Spreadsheet inheritance in Java:

 

In Java, our spreadsheet program has the same “topical” inheritance , but our other classes are gathered together into the common tree of Java classes.

The Java API makes a great deal more obvious use of inheritance than does C++ std.

In fact, until the fairly recent (Java 1.5) introduction of “generics” (similar to templates in C++), it was nearly impossible to do anything useful with Java data structures without exploiting inheritance.

Comparing the two diagrams, you can see why I said that Java programs are organized as one large inheritance tree, but C++ programs as a collection of small shrubs.

3.2 Inheriting from Object

If a class declaration does not explicitly state a superclass, by default it inherits from Object.

What do we get from this?

Some examples of the members we inherit:

3.2.1 What Can You do to Any Object?

We’ll encounter other Object functions later.

4 Overriding Functions

When a subclass inherits a function member, it may


Overriding: Declaring Your Intentions

overriding.h
class A {
public:
  void foo();
  void bar();
  void baz();
};

class B: public A {
public:
  void foo();       ➀
  void bar(int k);  ➁
  void bar() const; ➂
};                  ➃
  • B declares that it will override A::foo(). B inherits the declaration of foo() but will provide its own body.

  • This does not override A::bar().

    • Changing parameters overloads a function, but (access to) the original function is unaffected.
  • This does not override A::bar(), either. It also overloads it with different parameter types.

    • The implicit parameter this is a const B* instead of an A*.
  • Because B did not override A::bar() or A::baz(), it inherits those declarations and their bodies from A.


Access to Functions

B b;
b.foo();     // calls B::foo()
b.A::foo();  // calls the original A::foo()

void B::foo()
{
  A::foo();
  doSomethingExtra();
}


Example of Overriding

As an example of overriding, consider these four classes, which form a small inheritance hierarchy.

animalOv.cpp
class Animal {
public:
  String eats() {return "food";}
  String name() {return "Animal";}
};

class Herbivore: public Animal {
public:
   String eats() {return "plants";}
   String name() {return "Herbivore";}
};

class Ruminants: public Herbivore {
public:
   String name() {return "Ruminant";}
};

class Carnivore: public Animal {
public:
   String name() {return "Carnivore";}
};

void show (String s1, String s2) {
    cout << s1 << " " << s2 << endl;
}

Note that several of the inheriting classes override one or both functions in their base class.

Question: Now, suppose we run the following code. What will be printed by each of the show calls?

 Animal a;
 Carnivore c;
 Herbivore h;
 Ruminant r;
 show(a.name(), a.eats());        // AHRC fpgm
 show(c.name(), c.eats());        // AHRC fpgm
 show(h.name(), h.eats());        // AHRC fpgm
 show(r.name(), r.eats());        // AHRC fpgm

(Try to work this out for yourself before looking ahead.)

Answer:

Java programmers can similarly override member functions, though there are some other factors that come into play that we will cover later.


Inheritance and Encapsulation

An inheriting class does not get access to private data members of its base class:

C++

class Counter {
   int count;
public:
   Counter() {count = 0;}
   void increment() {++count;}
   int getC() const {return count;}
};

class HoursCounter: public Counter {
public:
   void increment() {
     counter = (counter + 1) % 24; // Error!
   }
};

Java:

public class Counter {
    private int count;

    public Counter() {count = 0;}
    public void increment() {++count;}
    public int getC() {return count;}
}

public class HoursCounter extends Counter {
   public void increment() {
     counter = (counter + 1) % 24; // Error!
   }
}

Protected Members

Data members marked as protected are accessible to inheriting classes but private to all other classes.

C++:

class Counter {
protected:
   int count;
public:
   Counter() {count = 0;}
   void increment() {++count;}
   int getC() const {return count;}
};

class HoursCounter: public Counter {
public:
   void increment() {
     counter = (counter + 1) % 24; // OK
   }
};

Java:

public class Counter {
    protected int count;

    public Counter() {count = 0;}
    public void increment() {++count;}
    public int getC() const {return count;}
}

public class HoursCounter extends Counter {
   public void increment() {
     counter = (counter + 1) % 24; // OK
   }
}

Package Members

Java has a fourth form of visibility, not available in C++.

If a Java declaration has no public/private/protected marker in front of it, then the member is package visibility. It is accessible to any other classes in hte same package, but not to other classes.

Java:

package Package1;
public class Counter {
    int count;  // package visibility

    public Counter() {count = 0;}
    public void increment() {++count;}
    public int getC() const {return count;}
}

package Package1;
public class HoursCounter1 extends Counter {
   public void increment() {
     counter = (counter + 1) % 24; // OK
   }
}

package Package2;
public class HoursCounter2 extends Counter {
   public void increment() {
     counter = (counter + 1) % 24; // ERROR
   }
}

5 Example: Inheritance and Expressions

Inheritance also plays a part in the spreadsheet in taking care of Expressions.


Expression Trees

 


Expression Inheritance Hierarchy

 

We would expect the lower-level classes like PlusNode and TimesNode to override the evaluate function to do addition, multiplication, etc., or whatever it is that distinctively identifies that particular “kind” of expression from all the other possibilities.


1: Photos courtesy of https://www.flickr.com/photos/photommo/17366900863 and https://www.nps.gov/glca/learn/nature/vascularplants.htm