You should be able to extend a class's behavior without modifying it.
The software entities such as classes, modules, functions etc. should be open for extensions but close for modifications.
So basically according to OCP, always create new classes instead of modifying it. We should strive to write a code that doesn't require modification every time a customer changes its request. Providing such a solution where we can extend the behavior of a class and not modify that class, should be our goal.
Example:
- Employee Salary Calculator without OCP
Lets create an Employee class first
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Level { get; set; }
public int WorkingHours { get; set; }
public double HourlyRate { get; set; }
}
public class SalaryCalculator
{
private readonly IEnumerable<Employee> _employees;
public SalaryCalculator(List <Employee> employees)
{
_employees = employees;
}
public double CalculateTotalSalaries()
{
double totalSalaries = 0D ;
foreach (var employee in _employees)
{
totalSalaries += employee.HourlyRate * employee.WorkingHours;
}
return totalSalaries;
}
}
static void Main(string[] args)
{
var employees = new List<Employee>
{
new Employee {Id = 1, Name = "Emp1", Level = "Permanent", HourlyRate = 100, WorkingHours = 40 },
new Employee {Id = 2, Name = "Emp2", Level = "Permanent", HourlyRate = 120, WorkingHours = 40 },
new Employee {Id = 3, Name = "Emp3", Level = "Temporary", HourlyRate = 60, WorkingHours = 60 }
};
var calculator = new SalaryCalculator(employees);
Console.WriteLine($"Sum of all the employees salaries is {calculator.CalculateTotalSalaries()} dollars");
}
All looks good so far!
Now consider if the HR comes back and asks us that we need different calculations for permanent and temporary employees, The Permanent employees should have bonus attached to their salaries.
A simple solution is to modify the CalculateTotalSalaries() method as following
public double CalculateTotalSalaries()
{
double totalSalaries = 0D;
foreach (var employee in _employees)
{
if(employee.Level == "Permanent")
{
totalSalaries += employee.HourRate * employee.WorkingHours * 1.2;
}
else
{
totalSalaries += employee.HourRate * employee.WorkingHours;
}
}
return totalSalaries;
}
However this is not an optimal solution as we have to modify existing function behavior and if the HR comes back and wanted to modify more parameters then we will have to change it again and it will make this method too complicated.
Now lets see the same example following Open and Closed principle
- Employee Salary Calculator With OCP
Lets create an abstract class first:
public abstract class BaseSalaryCalculator
{
protected Employee Employee { get; private set; }
public BaseSalaryCalculator(Employee employee)
{
Employee = employee;
}
public abstract double CalculateSalary();
}
Now create two classes inheriting from the BaseSalaryCalculator class
public class PermEmployeeSalaryCalculator : BaseSalaryCalculator
{
public PermEmployeeSalaryCalculator(Employee employee)
:base(employee)
{
}
public override double CalculateSalary() => Employee.HourlyRate * Employee.WorkingHours * 1.2;
}
public class TempEmployeeSalaryCalculator : BaseSalaryCalculator
{
public JuniorDevSalaryCalculator(Employee employee)
:base(employee)
{
}
public override double CalculateSalary() => Employee.HourlyRate * Employee.WorkingHours;
}
Now lets rewrite the SalaryCalculator class
public class SalaryCalculator
{
private readonly IEnumerable<BaseSalaryCalculator> _employeeCalculation;
public SalaryCalculator(IEnumerable<BaseSalaryCalculator> employeeCalculation)
{
_employeeCalculation = employeeCalculation;
}
public double CalculateTotalSalaries()
{
double totalSalaries = 0D;
foreach (var empCalc in _employeeCalculation)
{
totalSalaries += empCalc.CalculateSalary();
}
return totalSalaries;
}
}
static void Main(string[] args)
{
var employeeSalaryCalc = new List<BaseSalaryCalculator>
{
new PermEmployeeCalculator {Id = 1, Name = "Emp1", Level = "Permanent", HourlyRate = 100, WorkingHours = 40 },
new PermEmployeeCalculator {Id = 2, Name = "Emp2", Level = "Permanent", HourlyRate = 120, WorkingHours = 40 },
new TempEmployeeCalculator {Id = 3, Name = "Emp3", Level = "Temporary", HourlyRate = 60, WorkingHours = 60 }
};
var calculator = new SalaryCalculator(employeeSalaryCalc);
Console.WriteLine($"Sum of all the employees salaries is {calculator.CalculateTotalSalaries()} dollars");
}
Now this looks much better if HR comes to us to add new type of employee with different calculations or change how the particular employees salary is calculated.
So just add another calls for another level of employee, thus the SalaryCalculator is Closed for Modifications and Open for an extension
Summary
We used the Open Closed Principle (OCP) to calculate salaries of different types of employees. If the HR adds new type of employee with different calculations then all you need to do is provide a new implementation class inherited from the BaseSalaryCalculator which overrides CalculateSalary for salary calculation of new level of employee.