Software Engineering for Self-Directed Learners »

Paradigms → OOP →

Inheritance

What

Can explain the meaning of inheritance

The OOP concept Inheritance allows you to define a new class based on an existing class.

For example, you can use inheritance to define an EvaluationReport class based on an existing Report class so that the EvaluationReport class does not have to duplicate data/behaviors that are already implemented in the Report class. The EvaluationReport can inherit the wordCount attribute and the print() method from the base class Report.

  • Other names for Base class: Parent class, Superclass
  • Other names for Derived class: Child class, Subclass, Extended class

A superclass is said to be more general than the subclass. Conversely, a subclass is said to be more specialized than the superclass.

Applying inheritance on a group of similar classes can result in the common parts among classes being extracted into more general classes.

Man and Woman behave the same way for certain things. However, the two classes cannot be simply replaced with a more general class Person because of the need to distinguish between Man and Woman for certain other things. A solution is to add the Person class as a superclass (to contain the code common to men and women) and let Man and Woman inherit from Person class.

Inheritance implies the derived class can be considered as a sub-type of the base class (and the base class is a super-type of the derived class), resulting in an is a relationship.

Inheritance does not necessarily mean a sub-type relationship exists. However, the two often go hand-in-hand. For simplicity, at this point let us assume inheritance implies a sub-type relationship.

To continue the previous example,

  • Woman is a Person
  • Man is a Person

Inheritance relationships through a chain of classes can result in inheritance hierarchies (aka inheritance trees).

Two inheritance hierarchies/trees are given below. Note that the triangle points to the parent class. Observe how the Parrot is a Bird as well as it is an Animal.

Multiple Inheritance is when a class inherits directly from multiple classes. Multiple inheritance among classes is allowed in some languages (e.g., Python, C++) but not in other languages (e.g., Java, C#).

The Honey class inherits from the Food class and the Medicine class because honey can be consumed as a food as well as a medicine (in some oriental medicine practices). Similarly, a Car is a Vehicle, an Asset and a Liability.

Exercises



Overriding

Can explain method overriding

Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.

Consider the following case of EvaluationReport class inheriting the Report class:

Report methods EvaluationReport methods Overrides?
print() print() Yes
write(String) write(String) Yes
read():String read(int):String No. Reason: the two methods have different signatures; This is a case of overloading (rather than overriding).

Exercises



Overloading

Can explain method overloading

Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.

Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.

Example:

Method Type Signature
int add(int X, int Y) (int, int)
void add(int A, int B) (int, int)
void m(int X, double Y) (int, double)
void m(double X, int Y) (double, int)

In the case below, the calculate method is overloaded because the two methods have the same name but different type signatures (String) and (int).

  • calculate(String): void
  • calculate(int): void

Interfaces

Can explain interfaces

An interface is a behavior specification i.e. a collection of . If a class , it means the class is able to support the behaviors specified by the said interface.

There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts. --Oracle Docs on Java

Suppose SalariedStaff is an interface that contains two methods setSalary(int) and getSalary(). AcademicStaff can declare itself as implementing the SalariedStaff interface, which means the AcademicStaff class must implement all the methods specified by the SalariedStaff interface i.e., setSalary(int) and getSalary().

A class implementing an interface results in an is-a relationship, just like in class inheritance.

In the example above, AcademicStaff is a SalariedStaff. An AcademicStaff object can be used anywhere a SalariedStaff object is expected e.g. SalariedStaff ss = new AcademicStaff().


Abstract classes and methods

Can implement abstract classes

Abstract class: A class declared as an abstract class cannot be instantiated, but it can be subclassed.

You can declare a class as abstract when a class is merely a representation of commonalities among its subclasses in which case it does not make sense to instantiate objects of that class.

The Animal class that exists as a generalization of its subclasses Cat, Dog, Horse, Tiger etc. can be declared as abstract because it does not make sense to instantiate an Animal object.

Abstract method: An abstract method is a method signature without a method implementation.

The move method of the Animal class is likely to be an abstract method as it is not possible to implement a move method at the Animal class level to fit all subclasses because each animal type can move in a different way.

A class that has an abstract method becomes an abstract class because the class definition is incomplete (due to the missing method body) and it is not possible to create objects using an incomplete class definition.


Substitutability

Can explain substitutability

Every instance of a subclass is an instance of the superclass, but not vice-versa. As a result, inheritance allows substitutability: the ability to substitute a child class object where a parent class object is expected.

An AcademicStaff is an instance of a Staff, but a Staff is not necessarily an instance of an AcademicStaff. i.e. wherever an object of the superclass is expected, it can be substituted by an object of any of its subclasses.

The following code is valid because an AcademicStaff object is substitutable as a Staff object.

Staff staff = new AcademicStaff(); // OK

But the following code is not valid because staff is declared as a Staff type and therefore its value may or may not be of type AcademicStaff, which is the type expected by variable academicStaff.

Staff staff;
...
AcademicStaff academicStaff = staff; // Not OK

Dynamic and static binding

Can explain dynamic and static binding

Dynamic binding ( ): a mechanism where method calls in code are at , rather than at compile time.

Overridden methods are resolved using dynamic binding, and therefore resolves to the implementation in the actual type of the object.

Consider the code below. The declared type of s is Staff and it appears as if the adjustSalary(int) operation of the Staff class is invoked.

void adjustSalary(int byPercent) {
    for (Staff s: staff) {
        s.adjustSalary(byPercent);
    }
}

However, at runtime s can receive an object of any subclass of Staff. That means the adjustSalary(int) operation of the actual subclass object will be called. If the subclass does not override that operation, the operation defined in the superclass (in this case, Staff class) will be called.

Static binding (aka early binding): When a method call is resolved at compile time.

In contrast, overloaded methods are resolved using static binding.

Note how the constructor is overloaded in the class below. The method call new Account() is bound to the first constructor at compile time.

class Account {

    Account() {
        // Signature: ()
        ...
    }

    Account(String name, String number, double balance) {
        // Signature: (String, String, double)
        ...
    }
}

Similarly, the calculateGrade method is overloaded in the code below and a method call calculateGrade("A1213232") is bound to the second implementation, at compile time.

void calculateGrade(int[] averages) { ... }
void calculateGrade(String matric) { ... }