Question: Why and when should we use an abstract class? Provide an example of where we could use an abstract class.
Answer
Let's understand with an example. In an organization there are two types of employees FullTimeEmployee and contractEmployee.
For the first we will create a FullTimeEmployee class in a console application and provide the implementation of the class with the following procedure.
Step 1
Create a console application and name it InterviewQuestionPart3.
Step 2
Add a class file to the console application and provide it the name FullTimeEmployee.
Step 3
Provide the implementation for the class with the following code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- class FullTimeEmployee
- {
- public int ID { get; set;}
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int AnnualSalary { get; set; }
-
- public string GetFullName()
- {
- return this.FirstName + " " + LastName;
- }
-
- public int GetMonthlySalary()
- {
- return this.AnnualSalary/12;
- }
- }
- }
Step 4
Now create another employee, add another class file to the console application and provide the name contractEmployee.
Step 5
Provide the implementation for the class with the following code:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public class ContractEmployee
- {
-
- public int ID { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int HourlyPay { get; set; }
- public int TotalHours { get; set; }
-
-
- public string GetFullName()
- {
- return this.FirstName + " " + LastName;
- }
-
- public int GetMonthlySalary()
- {
- return this.TotalHours * this.HourlyPay;
- }
- }
- }
Step 6
Now if we need to use this classes in the programe.cs file, we have a Main method to create an instance of the FullTimeEmployee class and contractEmployee class and define several properties using the following code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- class Program
- {
- static void Main(string[] args)
- {
- FullTimeEmployee fte = new FullTimeEmployee();
- {
- fte.ID = 101;
- fte.FirstName="shaili";
- fte.LastName = "dashora";
- fte.AnnualSalary=60000;
-
- };
- Console.WriteLine(fte.GetFullName());
- Console.WriteLine(fte.GetMonthlySalary());
- Console.WriteLine("--------------");
-
-
-
- ContractEmployee cte = new ContractEmployee();
- {
- cte.ID = 101;
- cte.FirstName = "sourabh";
- cte.LastName = "somani";
- cte.HourlyPay = 200;
- cte.TotalHours = 40;
-
- };
- Console.WriteLine(cte.GetFullName());
- Console.WriteLine(cte.GetMonthlySalary());
-
- }
-
- }
- }
Step 7
Now see the output. The FullTimeEmployee full name is Shaili Dashora and the monthly salary is 5000 and the contractEmployee full name is Sourabh Somani and the monthly salary is 8000.
It is working fine as our expected output.
But the problem is that in this design, we see in these classes that both have a type of employee, some properties are also the same in both classes like employee ID, FirstName, LastName and the GetfullName method are all the same in both. The only difference is that here FullTimeEmployee has an Annual Salary and ContractEmployee has some more properties like hourly pay and total hours worked. That is the silly difference between both of the classes otherwise they are nearly similar.
So we have a lot of duplicated code here and code maintainability will be a problem because if we want to add a MiddleName property then we need to do that for both of the classes, whether you are a FullTimeEmployee or ContractEmployee you may have a middle name.
And in order to compute a fullName we need to introduce again the MiddleName in both of the classes. Here we have only two related classes but in reality depending on the types of objects that we are creating, we have several related classes.
Suppose in this example we have a part-time employee, a director employee or things like that so if we have more related classes then the number of places that we need to make changes will grow as well and that is vulnerable to error or is time consuming so code maintainability will be a big issue if we duplicate the code like this. So how to solve this problem?
Here two classes are related and some common functionality between them or some duplicate functionality exists between them, so to solve this problem we can move the common functionality from both of the classes into a base class and then we can inherit these two class from the base class.
But if we have design a base class for these two classes then the following two questions arise:
- Should we design it as an abstract class?
- Should we design it as a concrete (non-abstract) class?
Step 8
First let's design it as a concrete class and see what the problems that we encounter are.
Now we will add another class named BaseEmployee.
In this BaseEmployee class we will move the common properties from the FullTimeEmployee class like the ID, FirstName, LastName and GetFullName methods, these are common in both classes.
Now we will make the FullTimeEmployee class as inherited from the BaseEmployee class. So it has all the properties of the base class like the following code.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- class FullTimeEmployee:BaseEmployeeClass
- {
- public int AnnualSalary { get; set; }
- public int GetMonthlySalary()
- {
- return this.AnnualSalary/12;
- }
- }
- }
In a similar way we will make a ContractEmployee class that is also inherited from the BaseEmployee class and removes all the common properties that already exist in the base class.
Now the ContractEmployee class code will be like this:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public class ContractEmployee:BaseEmployeeClass
- {
- public int HourlyPay { get; set; }
- public int TotalHours { get; set; }
-
- public int GetMonthlySalary()
- {
- return this.TotalHours * this.HourlyPay;
- }
- }
- }
Step 9
Now in the BaseEmployee class we introduce one more method, GetMonthlySalary, and marked as virtual because the BaseEmploee class does not know how to provide the implementation for ContractEmployee or FullTime Employee. It is the responsibility of the derived classes to do that, thats why we marked it as virtual. And throw a NotImplementedException. Like the following code:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public class BaseEmployeeClass
- {
- public int ID { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
-
- public string GetFullName()
- {
- return this.FirstName + " " + LastName;
- }
-
- public virtual int GetMonthlySalary()
- {
- throw new NotImplementedException();
- }
-
-
- }
- }
Now in the derived classes we override the GetMonthlySalary method and provide the implementation of that specific class.
Now when we build the program we get the same output like this.
So here what we did is we moved all the common code in BaseEmployeeClass so if we want to introduce the middle name property we include it only within the Base class and it will be available for all the derived classes so code maintainability will be much easier.
So by introducing the non-abstract (concrete) class then we need to encounter the other problem that in our organization we have only two types of employees, FullTimeEmployee and ContractEmployee so in the Main method we are creating an instance of these employee classes and using them the way we want to.
Now the BaseEmployeeClass is the concrete class so we can create the instance of the BaseEmployeeClass and initialize the properties and access the methods then we build the program we did not get any compile time error.
But when we run this it will throw a run time exception because when we look at the BaseEmployeeClass GetMonthlySalary method. Here this class doesn't know the implementation of the method and throws a NotImplementedException when we run the program.
Step 10
That is one problem. Another problem is that in this concept of our organization we have only two types of employees FullTimeEmployee and ContractEmployee and they are nothing like BaseEmployee, it is only an abstract concept since we want the common functionality present to be in the base class. We moved the functionality into the BaseEmployeeClass but there is no BaseEmployee in our organization, so we don't want developers to create the instance of the BaseEmployeeClass. Using it like this, we want to prevent them from doing this, since we create class as non-abstract that is a concrete class so we are unable to stop the developers from doing this, that is the problem with using the non-abstract class here.
Now at the runtime we simply achieve the solution of this problem to remove the virtual method GetMonthlySalary from BaseEmployeeClass and remove the override keyword from the methods to the respected class like FullTimeEmployeeClass and ContractEmployeeClass.
And at the Main method we cannot invoke the GetMonthsalary Method so we remove the invocation.
Now we run the program and get the expected output like this:
But we still have the problem that we don't want the developers to create an instance of the BaseEmployee class since we do not have that type of employee. Here we want to prevent developers from doiing that. So for this we need to simply mark BaseEmployeeClass as Abstract like this.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public abstract class BaseEmployeeClass
- {
- public int ID { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
-
- public string GetFullName()
- {
- return this.FirstName + " " + LastName;
- }
- }
- }
Now at the Main method we have a compile time error like this:
So this is the advantage of creating a class as abstract, it prevents the accidental creation of an instance of the BaseEmployeeClass.
We cannot even compile them, we instead get the following compiler error.
Now we will comment out the instance of the BaseClassEmployee, then run it again and get good output.
Step 11
Now for some modifications that we will make to the BaseEmployeeClass. We will introduce the GetMonthlySalary method to this class and make it an abstract method so if we make the method as abstract then we do not need to provide the implementation, only a declaration and at the derived classes we will provide the implementation by overriding it.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public abstract class BaseEmployeeClass
- {
- public int ID { get; set; }
- public string FirstName { get; set; }
- public string LastName { get; set; }
-
- public string GetFullName()
- {
- return this.FirstName + " " + LastName;
- }
-
- public abstract int GetMonthlySalary();
-
-
- }
- }
The following is the code for ContractEmployeeClass.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- public class ContractEmployee:BaseEmployeeClass
- {
- public int HourlyPay { get; set; }
- public int TotalHours { get; set; }
-
- public override int GetMonthlySalary()
- {
- return this.TotalHours * this.HourlyPay;
- }
- }
- }
The following is the code for FullTimeEmployeeClass.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace InterviewQuestionPart3
- {
- class FullTimeEmployee:BaseEmployeeClass
- {
- public int AnnualSalary { get; set; }
- public override int GetMonthlySalary()
- {
- return this.AnnualSalary/12;
- }
- }
- }
Now we see the output is the same as the previous.
The reason we introduced the abstract method in the BaseEmployee class is to force all the derived classes to provide the implementation of the GetMonthlySalary abstract method. Otherwise we get a compilation error if we do not provide the implementation of the method in the derived classes, like this.
And it is good thing because we have a different type of employee and they have a different Monthly Salary. The abstract method forces us to provide the implementation in the derived classes.
Conclusion
So in this case we moved the common functionality from the FullTimeEmployee and contractEmployee classes into the BaseEmployeeClass and made it an abstract class because we don't want developers to accidentally create an instances of the abstract BaseEmployeeClass.
Now if our requirement is such that we want to instantiate the Base class then we need to use normal inheritance and not mark the class as an abstract class.
So it is a base class and two derived classes.