A Brief Overview of Generalisation and Inheritance

Francesco TusaFrancesco Tusa
6 min read

Introduction

When discussing the concepts of class and object in a previous article, we mentioned that objects of different classes can be interconnected through relationships. In object-oriented design, the meaning of relationships can extend beyond objects and involve the classes whereby all those objects are created. This occurs, for example, in scenarios where a general category (superclass or parent) has a relationship with a more specific type (subclass or child), termed generalisation or "is-a-kind-of" relationship.

Consider the example of generalisation where a general kind, Mammal, can relate with a more specific kind, such as a Dog, Cat, etc. Therefore, a Dog (or a Cat) "is-a-kind-of" Mammal. Let's assume a Dog class defines the attributes eyeColour, hairColour and barkFrequency, and the behaviours getEyeColour(), getHairColour() and bark(). Meanwhile, a Cat class defines the attributes eyeColour, hairColour and meowFrequency, and the behaviours getEyeColour(), getHairColour() and meow(). The following UML Class Diagram shows those three classes with their attributes, methods (behaviours) and relationships:

The diagram indicates the generalisation relationship via an arrow with an empty connector from the subclass to the superclass. However, even though we stated that a generalisation relationship exists between those classes, at the moment, the Mammal class does not define any attributes or methods. On the other hand, some common (duplicated) attributes and behaviours are defined in both subclasses (represented in bold).

We will now see how generalisation and inheritance can be used to improve the code organisation of these classes.

Designing Classes through Generalisation and Inheritance

Inheritance is an object-oriented principle manifested when two or more classes are related through generalisation. Specifically, common attributes and behaviours can be moved from the child classes (Dog and Cat in this case) to the parent class (Mammal in this case), facilitating code reuse among subclasses, as shown in the class diagram below:

In the diagram, the Mammal superclass now contains attributes (eyeColour, hairColour) and behaviours (getEyeColour(), getHairColour) common to the Dog and Cat subclasses.

The subclasses inherit these common features from the superclass and do not need to be defined again—they are described once in the Mammal superclass and can be reused by all the subclasses.

On the other hand, the subclasses can define specific attributes and behaviours pertaining solely to them, i.e., the barkFrequency and bark() for the Dog subclass and the meowFrequency and meow() for the Cat subclass.

Inheritance in Practice: A Code Example

Following the design in the previous class diagram, the Mammal class could be defined via the code below:

public class Mammal
{
    private String eyeColour;
    private String hairColour;

    public Mammal(string ec, string hc)
    {
        eyeColour = ec;
        hairColour = hc;
    }

    public String getEyeColour()
    {
        return eyeColour;
    }

    public String getHairColour()
    {
        return hairColour;
    }
}

Whereas the Dog class could be defined as follows:

public class Dog extends Mammal
{
    int barkFrequency;

    public Dog(string ec, string hc, int bf)
    {
        // attributes initialisation
    }

    public void bark()
    {
        // uses barkFrequency
    }

    // inherits getEyeColour and getHairColour from Mammal
    // no need to define them again here
}

In Java, the use of extends after the class name (Dog), followed by the superclass name (Mammal), indicates a generalisation relationship between those two classes. A similar concept exists in C#, but the symbol : is used instead of extends.

It is important to note that inheritance allows for code reuse, i.e., the code of getEyeColour and getHairColour does not need to be redefined in the Dog (Cat, or any) subclass. Moreover, any changes to these methods' code in the Mammal superclass will inherently be reflected in all subclasses.

In the previous Dog class definition, we deliberately did not provide the code within the constructor. The reason is that although a subclass inherits attributes and methods from a superclass, it still does not have direct access to private members and constructors of its superclass. Therefore, in the above code, the Dog constructor cannot access the eyeColour and hairColour attributes directly—how can the attributes initialisation be done?

Calling a Superclass Constructor: The super keyword

A constructor can be chained with another constructor of the same class using this. Similarly, a subclass constructor can call a superclass constructor and initialise private attributes of the superclass using the super keyword:

public class Dog extends Mammal
{
    int barkFrequency;

    public Dog(String ec, String hc, int bf)
    {
        super(ec, hc);
        barkFrequency = bf;
    }

    // ...
}

This allows the Dog constructor to invoke the Mammal constructor:

public Mammal(String ec, String hc)
{
    eyeColour = ec;
    hairColour = hc;
}

And initialise the private Mammal attributes eyeColour and hairColour, which can then be accessed by the Dog class through the inherited public methods getEyeColour and getHairColour.

It should be noted that a similar mechanism for invoking a superclass’ constructor from a subclass is available in C#, but the base keyword must be used in place of super.

Maximise Code Reuse: Defining Multiple Subclasses

Similarly, the Cat class can be defined using generalisation and inheritance via the following code:

public class Cat extends Mammal
{
    int meowFrequency;

    public Cat(String ec, String hc, int mf)
    {
        super(ec, hc);
        meowFrequency = mf;
    }

    public void meow()
    {
        // uses meowFrequency
    }

    // inherits getEyeColour and getHairColour from Mammal
    // no need to define them again here
}

As seen earlier, attributes and behaviours common for the subclasses are defined only once in the Mammal class and are inherited by the Dog and Cat subclasses. This leads to the advantage of reusing the same code and avoiding duplication. Moreover, if additional classes were to be defined for other mammals, such as Rabbit, Racoon, etc., they could all extend the Mammal class and reuse the code of getEyeColour and getHairColour through inheritance.

Conclusions

Let's conclude this article by discussing the usage of the Dog, Cat, and Mammal classes in the provided Program class example:

public class Program
{
    public static void main(String[] args)
    {
        // Create a Dog called Alan
        Dog alan = new Dog("Brown", "Short", 3);
        String alanEyeColour = alan.getEyeColour();
        alan.bark();

        // Create a Cat called Felix
        Cat felix = new Cat("Green", "Spotted", 5);
        string felixHairColour = felix.getHairColour();
        felix.meow();
    }
}

In the main method of the above Program class, we see the creation of two distinct objects—a Dog named alan and a Cat named felix—which both inherit characteristics from the common Mammal superclass.

Specifically, because of the existing generalisation relationship between the associated classes, both the Dog and Cat objects inherit common methods from the Mammal superclass, such as getEyeColour() and getHairColour(). This allows for code reuse by sharing common functionalities, as the alan and felix objects can call these methods directly. By defining methods like getEyeColour() and getHairColour() in the Mammal superclass, we avoid duplicating these methods in the Dog and Cat subclasses. This makes the code better organised and easier to maintain.

Finally, the program demonstrates how specific behaviours can be defined for each subclass while benefiting from the above-mentioned shared characteristics. For instance, after creating the Dog and Cat objects, we can call alan.bark() to trigger Alan's barking behaviour, and felix.meow() to make Felix meow. These methods are specific to the Dog and Cat classes, respectively, and highlight how subclass-specific functionalities can coexist with inherited methods.

0
Subscribe to my newsletter

Read articles from Francesco Tusa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Francesco Tusa
Francesco Tusa