Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was lead dev on a project for a logistics company. We were building a system to handle different types of shipping carriers—FedEx, UPS, DHL, and a few local couriers. Initially, the junior dev on the team created a separate class for every single carrier. It worked for a week, but then we realized we were duplicating the same 50 lines of logging and validation code in every single class. When we tried to fix it by creating a base Carrier class, we hit a snag: someone accidentally instantiated a generic Carrier object in the code, which made zero sense. You can't "ship" something via a generic carrier; it has to be a specific one.

That's exactly where abstract classes come in. They allow you to say, "This thing is a blueprint for other things, but it's too generic to exist on its own."

Bridging the Gap Between Interfaces and Concrete Classes

You've already worked with interfaces, which are basically strict contracts. An interface tells a class what it must do, but it doesn't care how it does it. On the flip side, a concrete class is a fully realized object. An abstract class is the middle ground. It lets you provide some shared implementation (so you aren't repeating yourself) while still forcing derived classes to fill in the blanks for the parts that are unique.

When you mark a class with the abstract modifier, you're telling the C# compiler that this class cannot be instantiated. If you try to call new PaymentProvider() on an abstract class, the compiler will stop you dead in your tracks. This is a safety feature. It ensures that your code only ever deals with specific, fully-implemented versions of that base concept.

Enforcing Rules with Abstract Members

Inside an abstract class, you can have regular methods with bodies—these are the "shared" behaviors. But you can also define abstract methods. An abstract method is a method signature without any implementation. It's essentially a hole in the class that must be filled by any non-abstract class that inherits from it.

Check out this example. We want every payment provider to log the transaction the same way, but the actual processing logic is totally different for a Credit Card versus PayPal.

public abstract class PaymentProvider
{
    // This is a concrete method. Every provider uses this exact logic.
    public void LogTransaction(string transactionId)
    {
        Console.WriteLine($"Logging transaction {transactionId} to the secure audit trail...");
    }

    // This is an abstract method. There is no "generic" way to process a payment.
    // We force the derived classes to figure this part out.
    public abstract bool ProcessPayment(decimal amount);
}

public class CreditCardProvider : PaymentProvider
{
    public override bool ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Charging {amount} to Credit Card via Gateway...");
        return true; 
    }
}

public class PayPalProvider : PaymentProvider
{
    public override bool ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Redirecting to PayPal API for {amount}...");
        return true;
    }
}

Notice the override keyword in the child classes. That's how C# knows you are intentionally fulfilling the contract set by the abstract base. I often tell people to think of the abstract class as the "Company Policy"—it defines the general workflow and the mandatory requirements, but leaves the specific execution to the individual employees (the derived classes).




📋 Practical Task

Build a Fleet Management Vehicle System

You need to create a system that handles different types of vehicles for a delivery fleet. Each vehicle has a common way of starting its engine, but the way they calculate their fuel efficiency is completely different based on their engine type.

Your requirements:

  • Create an abstract class named Vehicle.
  • Add a concrete method to Vehicle called StartEngine() that prints "Engine started!" to the console.
  • Add an abstract method to Vehicle called CalculateEfficiency() that returns a double.
  • Create two derived classes: Truck and ElectricCar.
  • In Truck, override CalculateEfficiency() to return 15.5 (representing MPG).
  • In ElectricCar, override CalculateEfficiency() to return 110.0 (representing MPGe).
  • In your Main method, create a List<Vehicle> containing one truck and one electric car. Iterate through the list, call StartEngine(), and print the result of CalculateEfficiency() for each.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.