The Open Closed Principle (OCP) is the SOLID principle which states that the software entities (classes or methods) should be open for extension but closed for modification.
But what does this really mean?
Basically, 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 (with that additional customer’s request) and not modify that class, should be our goal most of the time.
In this article, we will show you how to write the code by following the Open Closed Principle with two different examples. Initially, none of the examples will obey the OCP rules, but right after the initial development, we are going to refactor the code using the OCP.
To download the source code for this project, check out the Open Closed Principle Project Source Code.
To read about other SOLID principles, check out our SOLID Principles page.
So, let’s jump right into it.
Salary Calculator Example
Let’s imagine that we have a task where we need to calculate the total cost of all the developer salaries in a single company. Of course, we are going to make this example simple and focus on the required topic.
To get started, we are going to create the model class first:
public class DeveloperReport { 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; } }
Once we’ve created our model, we can transition to the salary calculation feature:
public class SalaryCalculator { private readonly IEnumerable<DeveloperReport> _developerReports; public SalaryCalculator(List <DeveloperReport> developerReports) { _developerReports = developerReports; } public double CalculateTotalSalaries() { double totalSalaries = 0D ; foreach (var devReport in _developerReports) { totalSalaries += devReport.HourlyRate * devReport.WorkingHours; } return totalSalaries; } }
Now, all we have to do is to provide some data for this class and we are going to have our total costs calculated:
static void Main(string[] args) { var devReports = new List<DeveloperReport> { new DeveloperReport {Id = 1, Name = "Dev1", Level = "Senior developer", HourlyRate = 30.5, WorkingHours = 160 }, new DeveloperReport {Id = 2, Name = "Dev2", Level = "Junior developer", HourlyRate = 20, WorkingHours = 150 }, new DeveloperReport {Id = 3, Name = "Dev3", Level = "Senior developer", HourlyRate = 30.5, WorkingHours = 180 } }; var calculator = new SalaryCalculator(devReports); Console.WriteLine($"Sum of all the developer salaries is {calculator.CalculateTotalSalaries()} dollars"); }
Our result should be:
So, all of this is working great, but now our boss comes to our office and says that we need a different calculation for the senior and junior developers. The senior developers should have a bonus of 20% on a salary.
Of course, to satisfy this requirement, we are going to modify our CalculateTotalSalaries
method like this:
public double CalculateTotalSalaries() { double totalSalaries = 0D; foreach (var devReport in _developerReports) { if(devReport.Level == "Senior developer") { totalSalaries += devReport.HourRate * devReport.WorkingHours * 1.2; } else { totalSalaries += devReport.HourRate * devReport.WorkingHours; } } return totalSalaries; }
Even though this solution is going to give us the correct result, this is not an optimal solution.
Why is that?
Mainly, because we had to modify our existing class behavior which worked perfectly. Another thing is that if our boss comes again and ask us to modify calculation for the junior dev’s as well, we would have to change our class again. This is totally against of what OCP stands for.
It is obvious that we need to change something in our solution, so, let’s do it.
Better Salary Calculator Example – OCP implemented
To create a code that abides by the Open Closed Principle, we are going to create an abstract class first:
public abstract class BaseSalaryCalculator { protected DeveloperReport DeveloperReport { get; private set; } public BaseSalaryCalculator(DeveloperReport developerReport) { DeveloperReport = developerReport; } public abstract double CalculateSalary(); }
As a continuation, we are going to create two classes which will inherit from the BaseSalaryCalculator class. Because it is obvious that our calculation depends on the developer’s level, we are going to create our new classes in that manner:
public class SeniorDevSalaryCalculator : BaseSalaryCalculator { public SeniorDevSalaryCalculator(DeveloperReport report) :base(report) { } public override double CalculateSalary() => DeveloperReport.HourlyRate * DeveloperReport.WorkingHours * 1.2; }
public class JuniorDevSalaryCalculator : BaseSalaryCalculator { public JuniorDevSalaryCalculator(DeveloperReport developerReport) :base(developerReport) { } public override double CalculateSalary() => DeveloperReport.HourlyRate * DeveloperReport.WorkingHours; }
Excellent. Now we can modify the SalaryCalculator
class:
public class SalaryCalculator { private readonly IEnumerable<BaseSalaryCalculator> _developerCalculation; public SalaryCalculator(IEnumerable<BaseSalaryCalculator> developerCalculation) { _developerCalculation = developerCalculation; } public double CalculateTotalSalaries() { double totalSalaries = 0D; foreach (var devCalc in _developerCalculation) { totalSalaries += devCalc.CalculateSalary(); } return totalSalaries; } }
This looks so much better because we won’t have to change any of our current classes if our boss comes with another request about the intern payment calculation or any other as well.
All we have to do now is to add another class with its own calculation logic. So basically, our SalaryCalculator
class is now closed for modification and opened for an extension, which is exactly what OCP states.
To finish this example, let’s modify the Program.cs
class:
class Program { static void Main(string[] args) { var devCalculations = new List<BaseSalaryCalculator> { new SeniorDevSalaryCalculator(new DeveloperReport {Id = 1, Name = "Dev1", Level = "Senior developer", HourlyRate = 30.5, WorkingHours = 160 }), new JuniorDevSalaryCalculator(new DeveloperReport {Id = 2, Name = "Dev2", Level = "Junior developer", HourlyRate = 20, WorkingHours = 150 }), new SeniorDevSalaryCalculator(new DeveloperReport {Id = 3, Name = "Dev3", Level = "Senior developer", HourlyRate = 30.5, WorkingHours = 180 }) }; var calculator = new SalaryCalculator(devCalculations); Console.WriteLine($"Sum of all the developer salaries is {calculator.CalculateTotalSalaries()} dollars"); } }
Awesome. We have finished our first example.
Let’s start with another one.
Filtering Computer Monitors Example
Let’s imagine for a moment that we have a task to write an application which gives us all the required information about computer monitors in our shop, based on different criteria. We will introduce only two criteria here, the type of monitors and the screen size. So let’s start with that:
public enum MonitorType { OLED, LCD, LED }
public enum Screen { WideScreen, CurvedScreen }
To continue, we are going to create a simple model class:
public class ComputerMonitor { public string Name { get; set; } public MonitorType Type { get; set; } public Screen Screen { get; set; } }
Now, we need to implement our filtering functionality. For example, we want to filter by the monitor types:
public class MonitorFilter { public List<ComputerMonitor> FilterByType(IEnumerable<ComputerMonitor> monitors, MonitorType type) => monitors.Where(m => m.Type == type).ToList(); }
And finally the Program.cs
class:
class Program { static void Main(string[] args) { var monitors = new List<ComputerMonitor> { new ComputerMonitor { Name = "Samsung S345", Screen = Screen.CurvedScreen, Type = MonitorType.OLED }, new ComputerMonitor { Name = "Philips P532", Screen = Screen.WideScreen, Type = MonitorType.LCD }, new ComputerMonitor { Name = "LG L888", Screen = Screen.WideScreen, Type = MonitorType.LED }, new ComputerMonitor { Name = "Samsung S999", Screen = Screen.WideScreen, Type = MonitorType.OLED }, new ComputerMonitor { Name = "Dell D2J47", Screen = Screen.CurvedScreen, Type = MonitorType.LCD } }; var filter = new MonitorFilter(); var lcdMonitors = filter.FilterByType(monitors, MonitorType.LCD); Console.WriteLine("All LCD monitors"); foreach (var monitor in lcdMonitors) { Console.WriteLine($"Name: {monitor.Name}, Type: {monitor.Type}, Screen: {monitor.Screen}"); } } }
This is going to work just fine. But, after a couple of days, we receive a request that our customers want to have the filter by Screen functionality as well.
So this should be quite simple, shouldn’t it?
Let’s just change the MonitorFilter
class:
public class MonitorFilter { public List<ComputerMonitor> FilterByType(IEnumerable<ComputerMonitor> monitors, MonitorType type) => monitors.Where(m => m.Type == type).ToList(); public List<ComputerMonitor> FilterByScreen(IEnumerable<ComputerMonitor> monitors, Screen screen) => monitors.Where(m => m.Screen == screen).ToList(); }
Even though this is going to give us the correct result, we have a problem because we have to modify our existing class. And what if we receive another request to filter all the monitors by type and screen together? We all see where this lead us, towards breaking the OCP. We are not extending our MonitorFilter
class but modifying it.
So, in order to avoid existing class modification, let’s try another approach.
Creating a couple of interfaces is going to be our first step:
public interface ISpecification<T> { bool isSatisfied(T item); }
public interface IFilter<T> { List<T> Filter(IEnumerable<T> monitors, ISpecification<T> specification); }
With the ISpecification
interface, we can determine whether or not our criterion is satisfied and we can send it to the Filter method from the IFilter
interface.
To continue on, we are going to create a separate class for the monitor type specification:
public class MonitorTypeSpecification: ISpecification<ComputerMonitor> { private readonly MonitorType _type; public MonitorTypeSpecification(MonitorType type) { _type = type; } public bool isSatisfied(ComputerMonitor item) => item.Type == _type; }
After this modification, all we have to do is to write a class that implements IFilter interface. But because we already have the MonitorFilter
class, we are just going to modify it:
public class MonitorFilter : IFilter<ComputerMonitor> { public List<ComputerMonitor> Filter(IEnumerable<ComputerMonitor> monitors, ISpecification<ComputerMonitor> specification) => monitors.Where(m => specification.isSatisfied(m)).ToList(); }
Finally, let’s modify the Program.cs
class:
var filter = new MonitorFilter(); var lcdMonitors = filter.Filter(monitors, new MonitorTypeSpecification(MonitorType.LCD)); Console.WriteLine("All LCD monitors"); foreach (var monitor in lcdMonitors) { Console.WriteLine($"Name: {monitor.Name}, Type: {monitor.Type}, Screen: {monitor.Screen}"); }
The result should be the same:
Additional Filter Requests
Right now, we are perfectly able to extend our MonitorFilter
class without any further modification. So, if now we have to implement the filter by screen feature, for example only widescreen monitors, we can do it with a new class:
public class ScreenSpecification : ISpecification<ComputerMonitor> { private readonly Screen _screen; public ScreenSpecification(Screen screen) { _screen = screen; } public bool isSatisfied(ComputerMonitor item) => item.Screen == _screen; }
And, we can make a call towards the MonitorFilter
class:
Console.WriteLine("All WideScreen Monitors"); var wideScreenMonitors = filter.Filter(monitors, new ScreenSpecification(Screen.WideScreen)); foreach (var monitor in wideScreenMonitors) { Console.WriteLine($"Name: {monitor.Name}, Type: {monitor.Type}, Screen: {monitor.Screen}"); }
Excellent.
With this project structure, we can even extend our filtering criterion to, for example, only OLED and widescreen monitors. All we have to do is to create another specification class.
Why Should We Implement the Open Closed Principle
By implementing the OCP we are lowering the chance of producing bugs in our project.
For example, if we have a fully working and already tested class in production, by extending it instead of changing it, we would definitely have a lesser impact on the rest of the system.
Therefore, we introduce another class to extend the behavior of the main class thus avoid the existing functionality modification that other classes may rely upon.
Another benefit is that we only have to test and deploy the new features, which wouldn’t be the case if we had to change existing functionality. Furthermore, if we decide that we don’t need this feature anymore (sometime in the future), all we have to do is to revert just newly implemented change and that’s it.
Conclusion
We’ve seen how the OCP can help us create better and more maintainable code. But, as with everything else, we should be cautious when implementing this principle.
Sometimes it’s just impossible to extend our class and all we are left to do is to modify existing functionality. We shouldn’t be afraid to do it, it is quite normal, but at least we should try to make those changes as discrete as they can be.
So, we should develop our applications with the OCP in mind and we should strive to write extendable code as much as we can because it leads to the maintainable, scalable and testable codebase.
And that’s what we want, isn’t it?
Good explanation. Thank you!
You are most welcome.
This is a great article. And my comment is nothing to do with your explanation, more to do with the open/closed principle in general. I appreciate this may be controversial but I just think in 90% of cases it should not be used, and here’s why. In my experience 90% of developers are working in small teams of 3-4 devs developing bespoke web applications or services. They aren’t developing the .net framework and they are not creating public nuget packages or contributing to huge open source projects. In these cases if they need to change the inner workings of a class they should just change it (and ensure suitable tests are written) as using open closed principle massively complicates the code, will take far longer to implement and will be much harder for juniors and new devs to understand. The benefits gained from O/C are almost never realised as not enough devs are using the code and the chances are if there is a significant requirement change the code would just get amended anyway.
just my thoughts from my experience.
Isn’t changing the contents of class MonitorFilter from public List<ComputerMonitor> FilterByType(IEnumerable<ComputerMonitor> monitors, MonitorType type) =>
monitors.Where(m => m.Type == type).ToList(); to public List<ComputerMonitor> Filter(IEnumerable<ComputerMonitor> monitors, ISpecification<ComputerMonitor> specification) =>
monitors.Where(m => specification.isSatisfied(m)).ToList(); considered modifying the existing class?
Thanks
Yes, but this is just in xontext of example, with this code you are saving yourself future modifications.
Thanks.
Great article! My observation from these examples is to identify or anticipate the moving parts which require specific implementation and separate out them as new classes with their own functionality encapsulated. Make the rest of the functionality as generic as possible using Abstract class or Interfaces.
Thanks for the amazing article.
In you second example, you have mentioned if the customer wants to add a new functionality filter, dev should modify MonitorFilter class, right?
please correct if I am wrong, according to description of OCP :
dev is extending the method not modifying it, is that right?
If yes, so using OCP in this example doesn’t make a sense.
MonitorFilter class is kind of Repository class, and a repository could be extended.
> In you second example, you have mentioned if the customer wants to add a new functionality filter, dev should modify MonitorFilter class, right?
No, I’ve shown how it could be done but it is not a good solution. Right after the code, I even wrote that. So, as you said, MonitorFilter shoud be extended and that’s exactly what I did with interfces and Specification classes.
thanks
Thanks a lot for your time and effort in providing high quality free education. The world is a better place now. (that people understand how to use the OCP)
Thank you Michail for reading and for the kind words. This means a lot to us.
Thank you so much for putting the effort into this article. I’ve tried so many times to understand Open/Close principle and to decide whether or not it’s worth the effort to implement it in our applications, but non of the articles I’ve read helped solve all my questions. Combining this article with my recent experience, this is the first time that I feel I actually understands the value behind OCP. Moreover, your principle of never follow blindly any of the SOLID principles is golden.
Well all I can do here is to put smile on my face and enjoy your comment 🙂 Thanks a lot for this comment and for reading our article. Feel free to come back any time you want 🙂 All the best.
Excellent article, thank you…
this site is very successful. pretty explained
Hello Fatih. You are very welcome. We are glad you like our site and our article. Best regards.
Good read! I really like the first example. I am struggling to accept the second example with filters though. It is all good in theory but when a developer has to work in that area of the code it will take much longer to figure out what is going on. Then you have all of these small files and new classes for every new specification. Add in DI for the ISpecification class and it is a complex puzzle to solve.
My view is that adding a method to a class is extending the class by adding functionality. You are not modifying any existing code. If there is ever a reason you would need to modify the filter functions, you would have to modify the ISpecification classes for the same reason. So what benefit does the added complexity actually give you?
Hello Pano, thank you for reading this article and adding a comment as well. I have one principle while working with SOLID principles and it is that one should never follow blindly any of the SOLID principles, they are here to guide us to create a better solutions but not to solve all of our problems. So, I have just created two examples to show how in different ways we can implement Open Closed principle in our code. I am totally agree with you that in a real life it is not that easy to implement all of this. Again, maybe you would expect me to disagree with your opinion, but it is quite opposite, I agree completely. Those examples are just my view on how OCP can be implemented in our code, and I am sure that many would think it is an overkill and again many wouldn’t. Bottom line is that all of this should be guide, and I am pretty sure that it will help our readers. One more time, thank you for your comment and best regards.
Excellent article and well explained. I have been struggling with SOLID principles for a year… Thank you so much ✌️
Good write up. Thanks!
You are very welcome.
Out of the 5 SOLID principles, for me, the Open/Closed principle was the hardest one to grasp. This post explains it really well and the code examples are very helpful.
Hello Dino. Thank you so much for the reading and commenting as well. We are so glad when hear something like that from a reader. Hope you can find some other articles useful as well. All the best and happy new year 😀