Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Abstract Class and its Usage

0.00/5 (No votes)
20 Dec 2013 1  
This article describes the concept of an abstract class in .NET.

Introduction

There is a lot of confusion among beginners about the abstract class. Knowing its syntax is easy, but when and why should we use an abstract class is something which has troubled most start up developers. I will try to explain the concept with an easy to understand example. Hope this proves to be useful!

Background

There are a lot of definitions for an abstract class, more often I find that instead of explaining the concept, articles deal with the actual syntax of the abstract class. For example, an abstract class cannot have a private virtual member, blah blah.

This article will try to explain the What and the When.

Understanding the Abstract Class

An abstract class is a class which cannot be instantiated, which means you cannot create an object of an abstract class, it can only act as a base class and other classes can derive from it. So what is the rationale behind the existence of a class if we cannot create an object of it?

Let’s take an example of a bank to explain this. Later, we will go through the code to implement it.

Suppose you go to a bank for opening an account. Let’s see below what transpires between an applicant and a bank staff.

Bank Staff: Welcome sir.

Applicant: I want to open an account.

Bank Staff: Sure sir, what kind of account do you want to open?

Applicant: I just want to open an account.

Bank Staff: Do you want to open a Savings Account or a Current Account?

Applicant: I do not want to open any specific account, just open an account.

Bank Staff: Sir, this is not possible, we need to create a specific type of account. Unless you tell me the type of the account, I cannot help you.

In the light of the above discussion, we come up with the below inference for our banking application software.

  • Banks have a facility to open an account
  • Bank account should be of a specific type (Saving/Current)
  • Bank cannot open a generic account

To explain this concept, let's create a banking application. This application is just for demonstration purposes and should not be used as a design guide.

Using the Code

We have a BankAccount class which is an abstract class for the reason explained above.

Irrespective of the type of account, there are certain members/behaviors which are common to all types of accounts and thus should be part of the base class, in our case the BankAccount class.

Bank Account Common Members
  • Account Owner property will have the name of the account holder
  • Account Number uniquely identifies a bank account
  • Minimum Balance will have the minimum threshold for the account
  • Maximum Deposit Amount will have the maximum amount which can be deposited in one go
  • Interest Rate is required for all bank accounts
  • Transaction Summary records all the transactions taking place in an account
Bank Account Common Behavior
  • You can deposit money in it.
  • You can withdraw money from it.
  • There should be facility to calculate interest.
  • User can generate Report/ Summary to see the transactions.

Deposit and Withdraw: These are two abstract methods. It looks like Deposit/Withdraw functionality should be the same for a SavingAccount and CurrentAccount but might not always be as in our case. These methods are abstract because we want that the child class should give its own implementation. (These two methods however can also be created as virtual methods but for the sake of our demonstration, let them be abstract methods.)

The CalculateInterest() method is implemented in the abstract class and the child class will reuse this functionality, clearly an advantage of an abstract class over an interface regarding code reuse:

public abstract class BankAccount
{
    // Name of the Account Owner, Its common for all derived classes
    public string AccountOwnerName { get; set; }

    // Account Number field is a common field for all the account types
    public string AccountNumber { get; set; }

    // A field to hold the Account Balance
    public decimal AccountBalance { get; protected set; }

    // A field to hold the MinimumAccount Balance
    protected decimal MinAccountBalance { get; set; }

    // A field to hold the Max Deposit Amount Balance
    protected decimal MaxDepositAmount { get; set; }

    protected decimal InteresetRate { get; set; } 
     
    // this variable will hold the summary of all the transaction that took place
    protected string TransactionSummary { get; set; }

    protected BankAccount(string accountOwnerName, string accountNumber)
    {
        AccountOwnerName = accountOwnerName;
        AccountNumber = accountNumber;
        TransactionSummary = string.Empty;
    }

    // Deposit is an abstract method so that Saving/Current Account must override
    // it to give their specific implementation.
    public abstract void Deposit(decimal amount);

    // Withdraw is an abstract method so that Saving/Current Account must override 
    // it to give their specific implementation.
    public abstract void Withdraw(decimal amount);
    
    public decimal CalculateInterest()
    {
        return (this.AccountBalance * this.InteresetRate) / 100;
    } 
   
    // This method adds a Reporting functionality 
    public virtual void GenerateAccountReport()
    {
        Console.WriteLine("Account Owner:{0}, Account Number:{1}, AccountBalance:{2}",
            this.AccountOwnerName, this.AccountNumber, this.AccountBalance);
   
        Console.WriteLine("Interest Amount:{0}", CalculateInterest());
        Console.WriteLine("{0}", this.TransactionSummary);
    }
} 

Constructor: Though the BankAccount class is an abstract class, it still has a constructor. What is the use of a constructor if an instance of the abstract class cannot be created? The constructor is used when we create an instance of SavingAccount or CurrentAccount, so variables defined in the abstract class could be initialized using the abstract class constructor. Remember that whenever a child class is instantiated, the constructor of its base class is called first and then the derived class constructor is called.

Some of the fields are protected and some are public, I have kept it like that for no serious reason. TransactionSummary is kept protected so that only a child class should be able to see and change it.

The GenerateAccountReport() method will show the details of the account including the transaction summary. It is a virtual method. By setting it as virtual, we are declaring that any subclass can override it to give its own implementation; however, the default implementation is provided by the base class.

Let's now move to our child classes, i.e., SavingAccount and CurrentAccount:

public class SavingBankAccount : BankAccount
{
    protected int withdrawCount = 0;

    public SavingBankAccount(string accountOwnerName, string accountNumber)
        :base(accountOwnerName,accountNumber)
    {
        this.MinAccountBalance = 20000m;
        this.MaxDepositAmount = 50000m;
        InteresetRate = 3.5m;
    }

    public override void Deposit(decimal amount)
    {
        if (amount >= MaxDepositAmount)
        {
            throw new Exception(string.Format("You can not deposit amount
                                 greater than {0}", MaxDepositAmount.ToString()));
        }

        AccountBalance = AccountBalance + amount;

        TransactionSummary = string.Format("{0}\n Deposit:{1}",
                                            TransactionSummary, amount);
    }

    public override void Withdraw(decimal amount)
    {
        // some hard coded logic that withdraw count should not be greater than 3
        if (withdrawCount > 3)
        {
            throw new Exception("You can not withdraw amount more than thrice");
        }

        if (AccountBalance - amount <= MinAccountBalance)
        {
            throw new Exception("You can not withdraw amount from your 
                                Savings Account as Minimum Balance limit is reached");
        }

        AccountBalance = AccountBalance - amount;
        withdrawCount++;

        TransactionSummary = string.Format("{0}\n Withdraw:{1}", 
                                            TransactionSummary, amount);
    }
    // This method adds details to the base class Reporting functionality 
    public override void GenerateAccountReport()
    {
        Console.WriteLine("Saving Account Report");
        base.GenerateAccountReport();
        
        // Send an email to user if Savings account balance is less 
        // than user specified balance this is different than MinAccountBalance
        if(AccountBalance > 15000) 
        { 
           Console.WriteLine("Sending Email for Account {0}", AccountNumber);
        }
    }
}

Let's see our CurrentAccount class which also derives from the abstract base class.

public class CurrentBankAccount : BankAccount
{
    public CurrentBankAccount(string accountOwnerName, string accountNumber)
        :base(accountOwnerName,accountNumber)
    {
        this.MinAccountBalance = 0m;
        this.MaxDepositAmount = 100000000m;
        InteresetRate = .25m;
    }

    public override void Deposit(decimal amount)
    {
        AccountBalance = AccountBalance + amount;
        TransactionSummary = string.Format("{0}\n Deposit:{1}",
                                            TransactionSummary, amount);
    }

    public override void Withdraw(decimal amount)
    {
        if (AccountBalance - amount <= MinAccountBalance)
        {
            throw new Exception("You can not withdraw amount from 
                           your Current Account as Minimum Balance limit is reached");
        }

        AccountBalance = AccountBalance - amount;
        TransactionSummary = string.Format("{0}\n Withdraw:{1}",
                                             TransactionSummary, amount);
    }
    // This method adds details to the base class Reporting functionality 
    public override void GenerateAccountReport()
    {
        Console.WriteLine("Current Account Report");
        base.GenerateAccountReport();
    }
} 

Let's dig inside our child classes. The constructors of the SavingAccount as well as CurrentAccount are initializing some variable as per their requirements, however certain common variables are set by the abstract class, which explains the rationale behind the need of a constructor in the abstract class.

The Withdraw and Deposit methods are pretty simple and need no detailed explanation. Both classes have overridden them to provide their own implementation.

The Deposit method of SavingAccount throws an exception if the amount deposited is greater than a specified limit.

The Withdraw method of SavingAccount checks the number of withdrawals before throwing an exception.

The GenerateAccountReport method of SavingAccount adds a report header, calls the base class method for the generic implementation, and then sends the account report email.

To use the above code, here is a our Main method which creates an instance each of a Saving and Current account. The variable taken to store these instance is of type BankAccount, thus allowing us to have polymorphic behavior.

public static void Main(string[] args)
{
    BankAccount savingAccount = new SavingBankAccount("Sarvesh", "S12345");
    BankAccount currentAccount = new CurrentBankAccount("Mark", "C12345");

    savingAccount.Deposit(40000);
    savingAccount.Withdraw(1000);
    savingAccount.Withdraw(1000);
    savingAccount.Withdraw(1000);
            
    // Generate Report
    savingAccount.GenerateAccountReport();

    Console.WriteLine();
    currentAccount.Deposit(190000);
    currentAccount.Withdraw(1000);
    currentAccount.GenerateAccountReport();

    Console.ReadLine();
}

Here is the output

Saving Account Report
Account Owner:Sarvesh, Account Number:S12345, AccountBalance:37000
Interest Amount:1295.0

 Deposit:40000
 Withdraw:1000
 Withdraw:1000
 Withdraw:1000
Sending Email for Account S12345

Current Account Report
Account Owner:Mark, Account Number:C12345, AccountBalance:189000
Interest Amount:472.50

 Deposit:190000
 Withdraw:1000

When to Use Abstract Class

Let's go back to the example of our banking application.

Though we cannot open a generic account, all accounts will have a certain member and behavior as discussed above. Also, we want that all types of accounts should conform to these attributes and behaviors.

To summarize, create an abstract class if:

  1. Class expresses an idea which is too generic and whose independent (alone) existence is not required in your application, e.g., BankAccount.
  2. There is a family of types forming a hierarchy. An “IS-A” relation exists between a base class and other derived classes. E.g.:
    • Saving Account IS-A Bank Account
    • Current Account IS-A Bank Account
  3. If there are certain members/ behaviors which are common to all classes, these should be placed inside the abstract class, e.g., AccountNumber, Deposit(), etc.
  4. If there are behaviors/attributes which should be implemented or must be present in all classes, declare them as abstract methods in the abstract class, e.g., CalculateInterest().

Why Not an Interface in Place of the BankAccount Class?

In our example, you can argue that we can use a Interface instead of the BankAccount abstract class, something like shown below:

public class SavingBankAccount : IBankAccount
{
    void Deposit(decimal amount);    
    void Withdraw(decimal amount);
    decimal CalculateInterest();
}

First of all, there is a hierarchy between BankAccount and SavingAccount and a close relation exists. Also, we are able to figure out certain common features present in all the child classes, thus an abstract class will help in code reuse. An interface is more of a contract between classes. There are a lot of syntactical differences between an abstract class and an interface, a little Googling may be of great help so I haven't touched it in this article.

Conclusion

I have explained what an abstract class is, how to use it, and when to use it with the help of an example.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here