C# Abstraction

Abstraction in C# allows us to hide the complex implementation details and expose only the essential functionality. Think of your TV remote - you just press buttons to make things happen, right? You don't need to know how the signals work inside.

When applied to a scenario like managing employees, you can use abstraction to define a general contract for employee-related operations while hiding the specific implementation details of different types of employees (like managers, developers, etc.).

In C#, abstraction is typically achieved using abstract classes or interfaces. Let’s look at how we can define abstraction with the example of an Employee class hierarchy.

Example 1: Using Abstract Classes for Employees

Here, we will create an abstract class Employee with abstract methods that each specific type of employee must implement.


using System;

// Abstract class
public abstract class Employee
{
    // Abstract method for calculating salary
    public abstract double CalculateSalary();

    // Method to display employee details
    public void DisplayDetails()
    {
        Console.WriteLine("Employee details are being displayed.");
    }
}

// Derived class for full time employees
public class FullTimeEmployee : Employee
{
    public double MonthlySalary { get; set; }

    public FullTimeEmployee(double monthlySalary)
    {
        MonthlySalary = monthlySalary;
    }

    // Implement the CalculateSalary method for the full time employees
    public override double CalculateSalary()
    {
        return MonthlySalary;
    }
}

//  Derived class for part time employees
public class PartTimeEmployee : Employee
{
    public double HourlyRate { get; set; }
    public double HoursWorked { get; set; }

    public PartTimeEmployee(double hourlyRate, double hoursWorked)
    {
        HourlyRate = hourlyRate;
        HoursWorked = hoursWorked;
    }

    // Implement the CalculateSalary method for part-time employees
    public override double CalculateSalary()
    {
        return HourlyRate * HoursWorked;
    }
}

public class Program
{
    public static void Main()
    {
        Employee fullTime = new  FullTimeEmployee(4000);
        Employee partTime = new PartTimeEmployee(20, 100);

        Console.WriteLine("Full-Time Employee Salary: $" + fullTime.CalculateSalary());// Outputs: 4000
        Console.WriteLine("Part-Time Employee Salary: $" + partTime.CalculateSalary());// Outputs: 2000
    }
}

Explanation:

  • Employee is an abstract class which includes an abstract method CalculateSalary(), which forces all the derived classes (like FullTimeEmployee and PartTimeEmployee) to provide their own implementation of this method.

  • The method DisplayDetails() is a regular method with the common implementation for all employees.

  • FullTimeEmployee class calculates salary based on a fixed monthly salary, while the PartTimeEmployee class calculates salary based on hourly rates and the number of hours worked.

Example 2: Using Interfaces for Employees

An interface is a contract that defines methods without implementing them. Any class that implements an interface must provide its own implementation of those methods. Using the similar Employee hierarchy example, you can use an interface to define the contract for employee-related functionality, where each employee type must implement the methods.


using System;

// Interface
public interface IEmployee
{
    double CalculateSalary();
    void DisplayDetails();
}

// Full-time employee implementing IEmployee interface
public class FullTimeEmployee : IEmployee
{
    public double MonthlySalary { get; set; }

    public FullTimeEmployee(double monthlySalary)
    {
        MonthlySalary = monthlySalary;
    }

    // Implement the CalculateSalary() method for the full-time employees
   public double CalculateSalary()
    {
        return MonthlySalary;
    }

    // Implement the DisplayDetails method for the full-time employees
    public void DisplayDetails()
    {
        Console.WriteLine("Full-Time Employee: Monthly Salary = $" + MonthlySalary);
    }
}

// Part-time employee implementing IEmployee interface
public class PartTimeEmployee : IEmployee
{
    public double HourlyRate { get; set; }
    public double HoursWorked { get; set; }

    public PartTimeEmployee(double hourlyRate, double hoursWorked)
    {
        HourlyRate = hourlyRate;
        HoursWorked = hoursWorked;
    }

    // Implement the CalculateSalary() method for the part-time employees
    public double CalculateSalary()
    {
        return HourlyRate * HoursWorked;
    }

    // Implement the DisplayDetails() method
    public void DisplayDetails()
    {
        Console.WriteLine("Part-Time Employee: Hourly Rate = $" + HourlyRate + ", Hours Worked = " + HoursWorked);
    }
}

public class Program
{
    public static void Main()
    {
        IEmployee fullTime = new FullTimeEmployee(5000);
        IEmployee partTime = new  PartTimeEmployee (50, 100);

        fullTime.DisplayDetails();  // Outputs: Full-Time Employee: Monthly Salary = $5000
        Console.WriteLine("Full-Time Salary: $" + fullTime.CalculateSalary());  // Outputs: 5000

        partTime.DisplayDetails();  // Outputs: Part-Time Employee: Hourly Rate = $50, Hours Worked = 100
        Console.WriteLine("Part-Time Salary: $" + partTime.CalculateSalary());  // Outputs: 5000
    }
}

Explanation:

  • IEmployee is an interface that defines the methods CalculateSalary() and DisplayDetails(). Any class that implements this IEmployee interface must provide its own implementation of these two methods.

  • FullTimeEmployee and PartTimeEmployee implement the IEmployee interface and provide their own specific logic for calculating salary and displaying employee’s details. .

Benefits of Abstraction in the Employee Example:

  1. Flexibility:New employee types (such as contractors or interns) can be easily added without altering the existing code. Simply implement the IEmployee interface or extend the Employee abstract class to introduce new variations.

  2. Loose Coupling: The client code doesn't need to be concerned with the specific type of employee. It interacts only with the IEmployee interface or the Employee abstract class, making the system more modular and adaptable to future changes.

  3. Code Reusability: Common functionality, like the DisplayDetails() method, can be reused by all employee types. At the same time, each specific employee type can implement its own salary calculation logic, ensuring that unique behaviors are preserved while shared code is reused.


Real-World Example 🌍

Think of a coffee machine:

  • You press a button → Get coffee

  • You don't need to know:

    1. How water is heated

    2. How beans are ground

    3. How milk is frothed

That's exactly what we're doing with our code! The complex details are hidden, but everything still works perfectly.

Conclusion:

Abstraction in C# enables you to focus on what an employee does (such as calculating salary or displaying details) while concealing the underlying implementation details (like whether the employee is full-time or part-time). You can implement abstraction using abstract classes or interfaces based on the specific needs of your application, providing flexibility and maintainability in your code design.