Conceptually, class A is a generalization of class B if every B object is also an A object.
“everything we say about an” A object “is also true for” a B object.
At the specification/implementation level, class A is a generalization of class B if B conforms to the public interface of A.
Specialization Example
For example, a check and a deposit are actually specializations of the more general concept of a “transaction”.
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
So if University Personnel have data members name and uin (University ID Number), the same will be true of Teachers
and Students
.
However, Teachers
may have data members not common to all UniversityPersonnel
, such as a salary.
Students
may likewise have data members not common to all UniversityPersonnel
, such as a grade point average gpa.
Inheritance Example
Teachers
and Students
, so GraduateTA
s will have a name, uin, and a salary and a gpa.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.
For example, does a GraduateTA
get one name or two?
One uin or two?
A closely related idea:
C is called a subclass or subtype of D.
D is called a superclass or supertype of C.
What’s the Difference?
Inheritance deals with the class’s members.
Subtyping deals with non-members
Effects of Subtyping and Inheritance
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); ➃
➀ We can make this call because apply is a member function of Transaction and transaction is of type Transaction.
Nothing special there.
➁ We can make this call because apply is a member function of Transaction, Check inherits from Transaction, and Check therefore has that same member function.
➂ We can make this call because applyToCurrentBalance is a non-member function that takes a Transaction as a parameter, and transaction is indeed of type Transaction.
➃ Finally, we can make this call because applyToCurrentBalance is a non-member function that takes a Transaction as a parameter, check is of type Check which is a subtype of Transaction, and we can use a subtype object in any operation where an object of the supertype is expected. Inheritance does not come into play in this case, because the function we are looking at is not a member function.
C++ Combines Inheritance & Subtyping
In most OOPLs, including C++, inheritance and subtyping are combined.
A base class is always a superclass.
An inheriting class is always a subclass.
A superclass is always a base class.
An subclass is always an inheriting class.
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.
The construct
class C : public Super {
indicates that
Inheritance
C inherits from Super
Super is a base class of C
Subtyping
C is a subtype of Super
Super is a supertype of C
#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
a formula (expression)
a value
Values
The interface for values is
#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.
#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
We infer by inheritance that NumericValue has function members render(), clone(), etc.
Therefore this code is OK (by inheritance):
void foo (NumericValue nv) {
cout << nv.render(8) << endl;
}
void foo (Value v; NumericValue nv) {
if (v == nv) {
⋮
String Values
String values hold numbers.
#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.
#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
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 {
What is different in Java is not how we do inheritance, but how often.
In C++, if we declare a new class and do not explicitly inherit from another, our new class has no base class.
C++ programmers tend to use inheritance only when there is a specific, application-driven need for it.
Inheritance in C++ like a handful of small shrubs.1 Some classes are tied to others by inheritance “branches”, but not many and not all together.
But in Java, if we do not explicitly inherit from another class, then our class will implicitly inherit from a class named Object
, more specifically, java.lang.Object
.
That ties all Java classes together into a single large tree.
Inheritance in Java is more like a banyan tree - tall and broad, with branches reaching over a vast area.1
In Java, if we do not explicitly inherit from another class, then our class will implicitly inherit from java.lang.Object
C++ programs use inheritance only rarely and when there is an obvious need for it.
Spreadsheet inheritance in C++:
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.
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:
protected native Object clone()
public boolean equals(Object)
finalize()
public final Class getClass()
public native int hashCode()
public String toString()
System.out.println (c.getName()
+ " has formula "
+ c.getFormula());
We’ll encounter other Object functions later.
When a subclass inherits a function member, it may
inherit the function’s body from the superclass, or
override the function by providing its own body
Overriding: Declaring Your Intentions
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().
➂ This does not override A::bar(), either. It also overloads it with different parameter types.
*
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.
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
Animal a;
Carnivore c;
Herbivore h;
Ruminant r;
show(a.name(), a.eats()); // Animal food
show(c.name(), c.eats()); // Carnivore food
show(h.name(), h.eats()); // Herbivore plants
show(r.name(), r.eats()); // Ruminant plants
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
}
}
Expression Trees
An Expression is represented as a tree
2*a1 + 3
An Expression comes in several parts
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