Lab: Overriding#

Objective#

In this lab, we will delve into the concept of overriding methods in object oriented programming. We will explore how to effectively use inheritance to modify the behavior of methods in derived classes. The focus will be on understanding the mechanics and implications of overriding methods in a class hierarchy.

Provided Code#

Carefully review the provided code. Notice that we have a base class named Sequence. This class includes a parameterless constructor and two methods: int Next() and int[] Take(int n). The Next method is virtual, allowing it to be overridden in derived classes, while Take is not. This design allows Next to be customized in subclasses, while Take maintains consistent behavior across all sequences.

class Sequence
{
    int n = 0;

    public Sequence () { }

    public Sequence (int n)
        => this.n = n;

    public virtual int Next()
        => n++;

    public int[] Take(int n)
    {
        int[] result = new int[n];
        for (int i = 0; i < n; i++)
            result[i] = Next();
        return result;
    }
}
Sequence seq = new Sequence();

foreach(int num in seq.Take(3))
    Console.WriteLine(num);
0
1
2
Sequence seq = new Sequence(50);

foreach(int num in seq.Take(3))
    Console.WriteLine(num);
50
51
52

Instructions#

Our goal is to create subclasses that inherit from Sequence and override the Next method to exhibit different behaviors.

Step 1: Even numbers#

Let’s begin by creating a subclass called EvenNumbers. It should override the method Next and only return even numbers. When you’re done you should be able to run the following code:

Sequence even1 = new EvenNumbers();

foreach(int num in even1.Take(3))
    Console.WriteLine(num);
2
4
6
Sequence even2 = new EvenNumbers(1000);

foreach(int num in even2.Take(3))
    Console.WriteLine(num);
1002
1004
1006

🤔 Reflection

How does overriding the Next method in the subclass alter the behavior of the Take method?

Step 2: Triangular numbers#

Let’s now create a subclass called TriangularNumbers that inherits from Sequence. Override the method Next and implement it so that it always return the next number in the sequence of triangular numbers. When you’re done you should be able to run the following code:

Sequence triangular = new TriangularSequence();

Console.WriteLine(String.Join(", ", triangular.Take(12)));
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78

Step 3: Fibonacci numbers#

Let’s now create a subclass called FibonacciSequence that inherits from Sequence. Override the method Next and implement it so that it always return the next number in the Fibonacci sequence. When you’re done you should be able to run the following code:

Sequence fibonacci = new FibonacciSequence();

Console.WriteLine(String.Join(", ", fibonacci.Take(12)));
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233

Step 4: Contrast to hiding#

Reflect over the differences between hiding and overriding by replacing the keyword override with the keyword new in all subclasses.

What does this change? How does this change the behavior of the code?

Challenge#

Let’s explore a more advanced application of our Sequence hierarchy that uses both inheritance, subtyping, and object composition at the same time.

Create a new class called SequenceSum. This class should have a constructor that takes a Sequence as an argument. The idea is that each time we ask the SequenceSum for the next number, it should ask the underlying sequence for the next number, add that to the currently running sum (starting from 0) and return the new sum.

When you’re done you should be able to run the following code:

Sequence naturals = new Sequence();
Sequence summedNaturals = new SequenceSum(naturals);

Console.WriteLine(String.Join(", ", summedNaturals.Take(6)));
0, 1, 3, 6, 10, 15
Sequence summedNaturals = new SequenceSum(new TriangularSequence());

for (int i=1; i<=5; i++)
{
    Sequence naturals = new TriangularSequence();
    string summands = String.Join(" + ", naturals.Take(i));
    int sum = summedNaturals.Next();
    Console.WriteLine($"{summands} = {sum}");
}
1 = 1
1 + 3 = 4
1 + 3 + 6 = 10
1 + 3 + 6 + 10 = 20
1 + 3 + 6 + 10 + 15 = 35