Inheritance and Polymorphism

Inheritance

The class that we have created allows us to create instances of a generic crop but it would be better if we could have specific types of crop such as wheat and potato. We could write new classes for each of these crop types from scratch but that would waste our existing work, especially considering that all crops have certain characteristics and behaviours in common.

Inheritance provides a mechanism that allows us to build on our existing work, ensuring that all crops share those characteristics and behaviours that they have in common. The relationship between classes benefiting from inheritance is similar to that of a parent and child.

A child class which inherits functionality from a parent class will have all of the same attributes and methods that the parent has. This means that if we want to change these attributes or methods we can do so in the parent class and the changes will be immediately available in all child classes. This is a major benefit compared to writing each class separately.

In addition, the child class can extend the functionality of the parent by adding attributes and methods that are unique to the child.

We can show the relationship between classes in an inheritance diagram:

inheritance diagram

Polymorphism

In a child class we can change how some methods work whilst keeping the same name. We call this polymorphism or overriding and it is useful because we do not want to keep introducing new method names for functionality that is pretty similar in each class. Consider our crop class, which has the method grow(). The pseudo-code for its current functionality is:

1
2
3
4
5
6
7
METHOD grow(light: integer, water: integer)
    IF light >= light_need AND water >= water_need THEN
        growth <- growth + growth_rate
    END IF
    days_growing <- days_growing + 1
    CALL update_status()
END METHOD

The Wheat child class would also need to grow depending on light and water but perhaps we would want to change how it grows so it more closely simulates how wheat grows in the real world:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
METHOD grow_wheat(light: integer, water: integer)
    IF light >= light_need AND water >= water_need THEN
        IF status = "Seedling" THEN
            growth <- growth + growth_rate * 1.5
        ELSE IF status = "Young" THEN
            growth <- growth + growth_rate * 1.25
        ELSE IF status = "Old" THEN
            growth <- growth + growth_rate / 2
        ELSE
            growth <- growth + growth_rate
        END IF
    END IF
    days_growing <- days_growing + 1
    CALL update_status()
END METHOD

In the above pseudo-code wheat grows different depending on what stage of its life-cycle it is in but we now have another method name to remember: grow_wheat().

This is unsatisfactory because child classes inherit all attributes and methods from their parent, so we now have two methods that can be called from our wheat class:

  • grow
  • grow_wheat

Obviously we do not want anyone to call the original grow() method as it has the generic growth algorithm and we have to remember that the grow method in the wheat class is not called grow() but grow_wheat().

Instead we override the functionality of the grow() method in our wheat class to make it work the way we want:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
METHOD grow(light: integer, water: integer)
    IF light >= light_need AND water >= water_need THEN
        IF status = "Seedling" THEN
            growth <- growth + growth_rate * 1.5
        ELSE IF status = "Young" THEN
            growth <- growth + growth_rate * 1.25
        ELSE IF status = "Old" THEN
            growth <- growth + growth_rate / 2
        ELSE
            growth <- growth + growth_rate
        END IF
    END IF
    days_growing <- days_growing + 1
    CALL update_status()
END METHOD

Now whenever the grow() method is called from an instance of the wheat class it will run the correct growth algorithm for wheat and because both the parent and child classes share the same name for the method that grows the crop it simplifies what we need to remember.

Adding inheritance and polymorphism

The video below demonstrates how to use inheritance and polymorphism to add classes to represent wheat and potato crops:


Task 7

Use the above video to help you add the wheat and potato sub-classes.


Updating our management program

The management program that we created in the previous section is now in need of updating - we need to have the ability to select the crop type that we want to instantiate before we can think about testing whether our new wheat and potato classes work as expected.

An updated structure chart might look as follows:

'Select crop structure chart'

The video below demonstrates how to update and improve the management program so that we can select the crop type we want to test:


Task 8

Use the above video to help you improve the management menu so that we can select different crop types to manage.


Practice makes perfect

Over the past several sections you have developed a simple simulation of a crop. One that reacts to the amount of light and water available (the weather) and grows based on these conditions. You have developed sub-classes that inherit functionality from your original crop class to create classes that more closely represent actual crop types: wheat and potato.

In the next section you will get to practice all of these skills as you develop a simulation to represent animals.