Practical Examples in a Music Theme


Enter base classes, virtual methods, and override functionality. I’ve been using this OOP design pattern quite a bit at work. These patterns are a fundamental layer in OOP and can help extend and make code very reusable (by eliminating unnecessary copy/paste!). As always, I wanted to explore these concepts through a verrrry simple music-themed lens. Let’s dive in.

Base Classes and Inheritance

So, a base class in C# defines shared properties and behaviors that derived classes inherit. Very straightforward and a good example is something like an Instrument class. An instrument can have countless variations and complexities, but will always be an instrument.

Example: The Instrument Base Class

Here’s a base class for instruments:

csharp
public class Instrument
{
    public string Name { get; set; }
    public int Volume { get; set; }

    public Instrument(string name, int volume)
    {
        Name = name;
        Volume = volume;
    }

    public virtual void Play()
    {
        Console.WriteLine($"{Name} produces sound at volume {Volume}.");
    }
}

The Instrument class includes Name and Volume properties and a virtual Play method (more on that in a second), which derived classes can override. This base class serves as a template for all instruments, keeping it consistent across all derived types, like guitars, drums, ukuleles, vuvuzelas, bassons, accordions, etc, etc, etc!

Virtual Methods and Override Functionality

So what does the virtual keyword in a base class mean? It marks a method as overridable! And that lets you override that specific method in a derived class. Boom! Surprise Polymorphism!

Example: Overriding with Guitar

Consider a Guitar class that inherits from Instrument:

csharp
public class Guitar : Instrument
{
    public int NumberOfStrings { get; set; }

    public Guitar(string name, int volume, int numberOfStrings)
        : base(name, volume)
    {
        NumberOfStrings = numberOfStrings;
    }

    public override void Play()
    {
        Console.WriteLine($"{Name}, a {NumberOfStrings}-string guitar, plays a melody at volume {Volume}.");
    }
}

As you can see, the Guitar class overrides the Play method to provide guitar-specific information, incorporating the number of strings.

Example: Overriding with Drums

Similarly, a Drums class can override Play:

csharp
public class Drums : Instrument
{
    public int NumberOfDrums { get; set; }

    public Drums(string name, int volume, int numberOfDrums)
        : base(name, volume)
    {
        NumberOfDrums = numberOfDrums;
    }

    public override void Play()
    {
        Console.WriteLine($"{Name}, a {NumberOfDrums}-piece drum kit, plays a rhythm at volume {Volume}.");
    }
}

Using the Base Keyword


Perhaps you’ve noticed, in the constructor of our derived Guitar and Drums classes, we aren't explicitly calling the Instrument class in the chain. That’s because we can use the base keyword! This lets us call the base class’s constructor (or methods), properly initializing and providing the derived class access to the virtual methods.

Example: Constructor Chaining

Here it is again for the folks in the back. In the Guitar class, the constructor uses base to initialize inherited members:

csharp
public Guitar(string name, int volume, int numberOfStrings)
    : base(name, volume)
{
    NumberOfStrings = numberOfStrings;
}

This ensures Name and Volume are set by the Instrument constructor before NumberOfStrings is initialized.

Band practice below!

Here’s how the classes work together:

csharp
class Program
{
    static void Main()
    {
        Instrument guitar = new Guitar("Stratocaster", 8, 6);
        Instrument drums = new Drums("Pearl Kit", 9, 5);

        guitar.Play();
        drums.Play();
    }
}

Output

Stratocaster, a 6-string guitar, plays a melody at volume 8.
Pearl Kit, a 5-piece drum kit, plays a rhythm at volume 9.

Breakdown (in conclusion)

  • Base Class: Instrument provides a common structure for properties and methods.
  • Virtual and Override: The virtual Play method allows derived classes to customize behavior, enabling polymorphism.
  • Base Keyword: Ensures proper initialization of inherited members, maintaining consistency.

This design pattern is available in most major OOP languages (if you’ve used Rails, you’ve seen it like this DerivedController > ApplicationController ) and something I just wanted to take a moment to reflect on. Up next, Access Modifiers and Abstraction. See you there!