DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Mastering C# Fundamentals: Abstract Classes vs Interfaces

Meta Description: Discover the differences between abstract classes and interfaces in C# through a practical financial example. Learn when to use each and how to create flexible, maintainable software with assignments to reinforce your understanding

In C# programming, developers often find themselves deciding between using abstract classes and interfaces. Both serve the purpose of defining a common set of behaviors that multiple derived classes can implement, but they are not interchangeable. In this article, we'll explore what abstract classes and interfaces are, their differences, when to use each, and we'll use a financial example to illustrate their use in a real-world scenario.

What Is an Abstract Class?

An abstract class is a class that cannot be instantiated directly. It serves as a blueprint for other classes. Abstract classes allow you to define:

  1. Abstract Methods: Methods that have no implementation and must be implemented by derived classes.
  2. Concrete Methods: Methods with implementation that can be inherited and used by derived classes.
  3. Fields and Properties: State information that derived classes can use.

Abstract Class Example: Financial Perspective

Let's create an abstract class BankAccount that represents a common blueprint for different types of bank accounts.

public abstract class BankAccount
{
    public string AccountNumber { get; set; }
    public decimal Balance { get; protected set; }

    public BankAccount(string accountNumber, decimal initialBalance)
    {
        AccountNumber = accountNumber;
        Balance = initialBalance;
    }

    // Abstract method that must be implemented by derived classes
    public abstract void CalculateInterest();

    // Concrete method shared across all accounts
    public void Deposit(decimal amount)
    {
        Balance += amount;
        Console.WriteLine($"{amount:C} deposited. Current balance: {Balance:C}");
    }

    public void Withdraw(decimal amount)
    {
        if (amount <= Balance)
        {
            Balance -= amount;
            Console.WriteLine($"{amount:C} withdrawn. Current balance: {Balance:C}");
        }
        else
        {
            Console.WriteLine("Insufficient balance.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In the BankAccount class:

  • The CalculateInterest method is abstract—it must be implemented by derived classes.
  • The Deposit and Withdraw methods are concrete—they are implemented and shared among all accounts.

What Is an Interface?

An interface defines a contract that implementing classes must follow. Interfaces only contain method signatures—there is no implementation. Interfaces are useful when you want multiple classes to share a common set of behaviors without sharing an implementation.

Interface Example: Financial Perspective

Let’s define interfaces that represent certain capabilities:

public interface ILoanAccount
{
    void ApplyForLoan(decimal amount);
    void PayLoan(decimal amount);
}

public interface IOnlineBanking
{
    void TransferFunds(string toAccount, decimal amount);
    void CheckOnlineBalance();
}
Enter fullscreen mode Exit fullscreen mode

These interfaces define behaviors (ApplyForLoan, TransferFunds, etc.) that can be shared by different types of accounts.

Combining Abstract Classes and Interfaces: Financial Example

Let’s create two classes: SavingsAccount and CreditAccount. Each inherits from BankAccount and implements relevant interfaces.

SavingsAccount: Inherits BankAccount and Implements IOnlineBanking

public class SavingsAccount : BankAccount, IOnlineBanking
{
    public SavingsAccount(string accountNumber, decimal initialBalance)
        : base(accountNumber, initialBalance)
    {
    }

    public override void CalculateInterest()
    {
        decimal interest = Balance * 0.03m;
        Balance += interest;
        Console.WriteLine($"Interest added: {interest:C}. New balance: {Balance:C}");
    }

    public void TransferFunds(string toAccount, decimal amount)
    {
        if (amount <= Balance)
        {
            Balance -= amount;
            Console.WriteLine($"{amount:C} transferred to account {toAccount}. Current balance: {Balance:C}");
        }
        else
        {
            Console.WriteLine("Insufficient balance for transfer.");
        }
    }

    public void CheckOnlineBalance()
    {
        Console.WriteLine($"The current balance for account {AccountNumber} is {Balance:C}");
    }
}
Enter fullscreen mode Exit fullscreen mode

CreditAccount: Inherits BankAccount and Implements ILoanAccount

public class CreditAccount : BankAccount, ILoanAccount
{
    public decimal LoanBalance { get; private set; }

    public CreditAccount(string accountNumber, decimal initialBalance)
        : base(accountNumber, initialBalance)
    {
        LoanBalance = 0;
    }

    public override void CalculateInterest()
    {
        decimal interest = LoanBalance * 0.05m;
        LoanBalance += interest;
        Console.WriteLine($"Interest on loan added: {interest:C}. Current loan balance: {LoanBalance:C}");
    }

    public void ApplyForLoan(decimal amount)
    {
        LoanBalance += amount;
        Console.WriteLine($"{amount:C} loan approved. Current loan balance: {LoanBalance:C}");
    }

    public void PayLoan(decimal amount)
    {
        if (amount <= LoanBalance)
        {
            LoanBalance -= amount;
            Console.WriteLine($"{amount:C} paid towards loan. Current loan balance: {LoanBalance:C}");
        }
        else
        {
            Console.WriteLine("Overpayment not allowed.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Demonstration

Let’s see how we can use these classes:

public class Program
{
    public static void Main(string[] args)
    {
        // Create a SavingsAccount
        SavingsAccount savings = new SavingsAccount("SA12345", 1000m);
        savings.Deposit(500m);
        savings.CalculateInterest();
        savings.TransferFunds("SA54321", 200m);
        savings.CheckOnlineBalance();

        Console.WriteLine();

        // Create a CreditAccount
        CreditAccount credit = new CreditAccount("CA98765", 2000m);
        credit.ApplyForLoan(1000m);
        credit.CalculateInterest();
        credit.PayLoan(500m);
        credit.Withdraw(1000m);
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

$500.00 deposited. Current balance: $1,500.00
Interest added: $45.00. New balance: $1,545.00
$200.00 transferred to account SA54321. Current balance: $1,345.00
The current balance for account SA12345 is $1,345.00

$1,000.00 loan approved. Current loan balance: $1,000.00
Interest on loan added: $50.00. Current loan balance: $1,050.00
$500.00 paid towards loan. Current loan balance: $550.00
$1,000.00 withdrawn. Current balance: $1,000.00
Enter fullscreen mode Exit fullscreen mode

Abstract Class vs Interface: Key Differences

Feature Abstract Class Interface
Inheritance Can inherit only one abstract class. Can implement multiple interfaces.
Methods Can have both abstract and concrete methods. Can only have abstract methods (except for default implementations).
Fields Can contain fields. Cannot have fields.
Access Modifiers Can use different access modifiers. All members are implicitly public.
Constructor Can have constructors. Cannot have constructors.
Use Case Used when classes share a common base and behavior. Used for defining capabilities that multiple classes can share.

When to Use Abstract Class vs Interface

Use Abstract Class When:

  1. You have shared code or default behavior that should be inherited by all derived classes.
  2. You need to define fields or properties.
  3. You want to provide a common base class that multiple classes can share.

Use Interface When:

  1. You need to define a set of capabilities or behaviors that can be shared by multiple unrelated classes.
  2. You want to use multiple inheritance (a class can implement several interfaces, while it can only inherit from one abstract class).
  3. You want to ensure that a class provides specific functionality but don't care how that functionality is implemented.

Assignments

To reinforce your understanding of abstract classes and interfaces in a financial context, try the following exercises:

Easy Level

  1. Create an abstract class called FinancialAccount with properties AccountHolderName and Balance.
  2. Add an abstract method called CalculateFee() and a concrete method called Deposit() to the FinancialAccount class.
  3. Implement a FixedDepositAccount class that inherits from FinancialAccount and provides an implementation for CalculateFee().

Medium Level

  1. Create two interfaces: IOverdraftAccount with methods RequestOverdraft() and RepayOverdraft(), and ISavingsBonus with a method AddBonus().
  2. Create a class SavingsPlusAccount that inherits from the FinancialAccount class and implements both IOverdraftAccount and ISavingsBonus interfaces.
  3. Write a short program to create an instance of SavingsPlusAccount and call all the implemented methods.

Difficult Level

  1. Create an abstract class InvestmentAccount with methods Invest() and an abstract method CalculateReturns().
  2. Create two interfaces: IRiskAssessment with a method AssessRisk() and IDividend with a method PayDividend().
  3. Implement a class StockInvestmentAccount that inherits from InvestmentAccount and implements both interfaces, providing concrete implementations for all methods.
  4. Write a program that creates multiple types of investment accounts (StockInvestmentAccount, BondInvestmentAccount, etc.), demonstrates shared functionality, and shows how different investment

products handle risk assessment and dividend payments.

Summary

  • Abstract Classes provide a shared template for different entities, allowing common code and abstract methods to be inherited.
  • Interfaces define specific capabilities that various classes can have, allowing for multiple inheritance of behaviors.
  • By combining abstract classes and interfaces, you can create financial software that is robust, reusable, and easy to maintain.

In this example, we used abstract classes and interfaces to develop a flexible financial system involving different account types with distinct capabilities. Understanding when to use each will help you design software that is scalable and easy to understand.

Top comments (0)