Inheritance

Inheritance #

Subclass #

In most (class-based) object-oriented languages, a class $A$ can extend another class $B$. In this case, $A$ is called a subclass of $B$.

The intuitive meaning is inclusion ($\subseteq$) between their respective sets of instances, i.e. every instance of $A$ is also an instance of $B$ (but the converse may not hold).

Hint.

$\qquad$ “$A$ extends $B$”

can be paraphrased as

$\qquad$ “every $A$ is a $B$”.

Examples.

  • every banana is a fruit,
  • every square is a rectangle,
  • every rectangle is a polygon.

In each of the following cases, could $A$ extend $B$ ?

$A$ $B$
Student Vegetarian
Polygon Hexagon
Continent Country
Country Continent
City Country
ZoomMeeting Meeting
ZoomMeeting Calendar
Chapter Book
Minute TimeInterval
Hour TimeInterval
Minute Hour
Integer RationalNumber
RationalNumber RealNumber
Integer RealNumber
SetOfStudents Student
Student SetOfStudents
SetOfStudents Set
SetOfStudents SetOfPeople
ArrayOfIntegers ArrayOfRealNumbers
ArrayOfIntegers SetOfIntegers
UnionOfSets Set
Set UnionOfSets
Tree Graph
Graph Tree
AcyclicGraph Tree
Object Class
Subclass Class
Class Subclass

Transitivity #

If $A$ extends $B$ and $B$ extends $C$, then $A$ extends $C$

Example. In the exercise above, Integer extends RationalNumber and RationalNumber extends RealNumber, therefore Integer extends RealNumber.

Antisymmetry #

If $A$ extends $B$ and $B$ extends $A$, then they are the same class.

Example. In the exercise above, Set and UnionOfSets are (arguably) the same class.

Inheritance #

Naturally, if $A$ extends $B$, then it inherits the properties of $B$.

Example. A person has a birth date.

Since every student is a person, a student also has a birth date.

Example. A rectangle has four right angles.

Since every square is a rectangle, a square also has four right angles.

Factorizing code with a superclass (in Java) #

Inheritance can be used to avoid redundant code.

Running example #

Example. We will use an imaginary game, where each non-player character (NPC) has:

  • a type: unicorn, butterfly, fairy, …,
  • a certain amount of experience (XP),
  • a certain number of remaining moves.

Besides, some properties and behaviors of an NPC are dictated by its type (e.g. its initial XP).

We will model the NPCs of this game as objects.

First, we can create a class Unicorn whose instances are all NPCs of type unicorn.
In Java:

public class Unicorn {
    int xp;
    int moves;

    public Unicorn(int moves) {
        xp = 5;
        this.moves = moves;
    }
}

Note. In this example, we use the prefix this. for the attribute moves only, because there is no ambiguity for the other attribute.

We also create a class Butterfly on the same model:

public class Butterfly {
    int xp;
    int moves;

    public Butterfly(int moves) {
        xp = 2;
        this.moves = moves;
    }
}

Implement a method

void meets(Unicorn u1, Unicorn u2)

that manages a meeting between two unicorns, with the effect that each unicorn gains the other’s XP.

For instance, if u1.xp has value 5 and u2.xp has value 7, then after the meeting, u1.xp and u2.xp both have value 12.

Example (continued). Let us assume that we also want to manage meetings between:

  • a unicorn and a butterfly and
  • a butterfly and a butterfly,

with the same effect as a meeting between two unicorns.

Without inheritance, one would need to implement two additional (nearly identical) versions of the method meets.

More generally, if the game has $n$ types of NPCs, then the program would contain $\frac{n(n-1)}{2} + n$ nearly identical meets methods.

Question. Can we use inheritance in this example to avoid duplicate code (and how)?

Example (continued). Observe that a unicorn and a butterfly (viewed as object) have identical attributes, namely int xp and int moves. So we can create a superclass NPC of Unicorn and Butterfly that carries these attributes, and let the two subclasses inherit them.

Abstract class #

Example (continued). We may also force every NPC in the game to have a concrete type (like “unicorn” or “butterfly”), rather than being a generic “NPC”.

Syntax. In Java, the keyword abstract ensures that a class cannot be directly instantiated (even though it can have a constructor).

Notation. In the diagram above:

  • the blue circled “A” indicates an abstract class,
  • the green circled “C” indicates a regular (i.e. directly instantiated) class.

Example (continued). Here is a possible implementation of the abstract class NPC

public abstract class NPC {
    int xp;
    int moves;

    public NPC(int xp, int moves) {
        this.xp = xp;
        this.moves = moves;
    }
}

Because this class is abstract, the following code does not compile:

// Compilation error: the class is abstract, so it cannot be directly instantiated.
NPC c = new NPC(2,3);

Subclass #

Syntax.

  • the keyword extends lets us declare that a class is a subclass of another
  • the keyword super lets us use the constructor of the superclass inside the subclass.

Example (continued). We can now rewrite our class Unicorn, as follows:

  • declare that Unicorn is a subclass of NPC, with extends, and
  • (optionally) use the constructor of NPC within the constructor of Unicorn, with super.

This yields:

public class Unicorn extends NPC {

    public Unicorn(int moves) {
        super(5, moves);
    }
}

And we can proceed similarly for the class Butterfly:

public class Butterfly extends NPC {

    public Butterfly (int moves) {
        super(2, moves);
    }
}

Restriction. In Java (as opposed to C++ for instance), a class can only have one immediate superclass.

So the keyword super is never ambiguous.

Example (continued). Both attributes (xp and moves) are now carried by the superclass NPC.

Because they are inherited, these attributes can still be accessed as if they were regular attributes of the subclass.
For instance,

Unicorn u = new Unicorn(3);
System.out.println(u.xp);

outputs

5

This allows us to write a generic method meets, as follows:

void meets(NPC c1, NPC c2) {

    int tmp = c1.xp;
    c1.xp += c2.xp;
    c2.xp += tmp;
}

And this method can be called with unicorns and/or butterflies as inputs.
For instance:

Unicorn u = new Unicorn(1);
Butterfly b = new Butterfly(4);
meets(u, b);

Transitive inheritance #

Example (continued). In the example above, we assumed that all NPCs can move.

Let us now add a type of NPCs called Flower, which cannot move. An instance of this class does not need the attribute moves.

In order to model this, a quick solution consists in setting moves to a special value (e.g. -1) for the class Flower.
This is unsatisfactory, because:

  • unnecessary attributes make code harder to understand, and
  • this is a potential source of bugs, when the attribute moves for a flower is accidentally accessed.

Modify our model to accommodate for the class Flower, so that an instance of Flower only has the xp attribute, with (default) value 3.