top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

  • Writer's pictureAhmed Tarek

Secrets of the Single Responsibility Principle

Uncover the secrets about the Single Responsibility Principle (SRP) of the SOLID Principles.


Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Secrets of the Single Responsibility Principle. Image by Ahmed Tarek

Almost all developers working with Object Oriented Programming (OOP) languages know about the SOLID principles.


SOLID Principles

  • Single Responsibility Principle

  • Open-closed Principle

  • Liskov Substitution Principle

  • Interface Segregation Principle

  • Dependency Inversion Principle


In this article, we are going to explain the S of the SOLID, Single Responsibility Principle, however, what you are going to read here is different…


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Romain Vignes on Unsplash, adjusted by Ahmed Tarek

What is the Single Responsibility Principle


According to Wikipedia:

The term was introduced by Robert C. Martin in his article “The Principles of OOD” as part of his Principles of Object Oriented Design, made popular by his 2003 book Agile Software Development, Principles, Patterns, and Practices.
Martin described it as being based on the principle of cohesion, as described by Tom DeMarco in his book Structured Analysis and System Specification, and Meilir Page-Jones in The Practical Guide to Structured Systems Design.
In 2014 Martin published a blog post titled “The Single Responsibility Principle” with a goal to clarify what was meant by the phrase “reason for change.”

And for its meaning:

Robert C. Martin expresses the principle as, “A class should have only one reason to change”. Because of confusion around the word “reason” he has also clarified saying that the “principle is about people.” In some of his talks, he also argues that the principle is, in particular, about roles or actors.
For example, while they might be the same person, the role of an accountant is different from a database administrator. Hence, each module should be responsible for each role.

So now enough with the Wikipedia talk, let’s clarify some points.


The main definition of the Single Responsibility Principle is that a class should have only one reason to change.


People might get confused about what is meant by reason and I totally understand where this is coming from.


Let me elaborate…


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Goh Rhy Yan on Unsplash, adjusted by Ahmed Tarek

Important Note


To drive your focus to the main topic we are discussing here, some code best practices would be dropped. This means fewer interfaces, abstractions, implementing constructors,… unless needed for the demonstration.


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Uday Mittal on Unsplash, adjusted by Ahmed Tarek

The Confusion


Some developers tend to say that if a class is doing too many things, it is actually violating the Single Responsibility Principle.


Good, but still, what is meant by too many? How to know if a class is doing too many things or not? If a class is doing 2 things, is this too many? What about 3? What about 4? …


In my humble opinion, I find this definition confusing and I can’t blame you if you feel the same. The “too many” term is neither measurable nor understandable.


I can hear you now saying:

Then what, do we agree that there is no such good term to describe the Single Responsibility Principle? Should we just drop it?

Actually no. I have something that could make it more clear so let me elaborate more.


In software, a method -or a function, whatever you like to call it- has two factors to describe it:

  • Knowledge required to do its job.

  • Achievement done when it is finished.


These two factors are different, they are not the same. We should distinguish one from another as there are a lot of consequences based on how each of them scales.


Now, let me walk you through it step by step.


 

Method Knowledge


Refers to the know-how the method needs to have to be able to do its job. In other words, it is referring to the internal details and steps the method needs to know so that it can get its job done.


For example, let’s have a look at this method:


public int Add(int a, int b)
{
  return a + b;
}

The method Add should know how to add two numbers. It does it by using the + operator provided by the .NET Framework.


Now, if the developers of the .NET Framework decide to change the internal implementation of the + operator or how it is translated on the machine code level, this should not affect the Add method as it doesn’t care. It just delegates the methodology of adding two integer numbers to the .NET Framework.


Now, let’s assume that the requirements of the system we are developing are changed so that we need to add an extra margin to the Add method.


So, the code should be as follows:


private const int Margin = 10;

public int Add(int a, int b)
{
  return a + b + Margin;
}

Now, this is different.


Following this new implementation of the Add method, the knowledge of the method itself has changed, and the know-how has changed as now it needs to know about adding an extra margin which was not there before.


 

Method Achievement


Refers to the work that would be done when the method is finished regardless if the method has the absolute know-how of each step of this work or not.


For example, let’s have a look at this method:


public class EmployeeManager
{
  private readonly EmployeeRepository _mEmployeeRepository;
  private readonly TaxCalculator _mTaxCalculator;
  private readonly MailingGroupsManager _mMailingGroupsManager;
  private readonly HolidaysCalculator _mHolidaysCalculator;

  public async Task<Employee> HireEmployee(Employee employee)
  {
    // Add Employee entry with basic info in DB
    var updatedEmployee = await _mEmployeeRepository.Add(employee);
    
    // Add Emplyee to mailing groups
    updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee);
    
    // Calculate tax scheme
    decimal tax = await _mTaxCalculator.Calculate(updatedEmployee);
    
    // Update tax scheme in DB
    updatedEmployee = await _mEmployeeRepository.UpdateTax(updatedEmployee, tax);
    
    // Calculate holidays
    byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee);
    
    // Update holidays in DB
    return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount);
  }
}

As we can notice here, the amount of work that would be achieved after this method finishes would be huge. However, can we say the same about the knowledge? I don’t think so…


The only knowledge this method has is the knowledge about the steps and order of the actions needed to get an Employee hired, nothing more.


You might argue that the method knows more than that but actually no. Yes, when the HireEmployee method starts an employee would be added to the database, but actually, it doesn’t know how this is done, the Add method of the EmployeeRepository class is the one that owns this knowledge.


The same applies to all of the other methods and this leaves the HireEmployee method simpler than what we could have expected before noticing this, right?


 

Having said that, let’s now analyze how these two factors could have an impact on the definition of the Single Responsibility Principle.


By now, it should be obvious that when we are evaluating if a class is abiding by the Single Responsibility Principle or not, we should be more concerned about the knowledge than the achievement.


This is because the more the knowledge a class has, the more probable it would violate the Single Responsibility Principle.


Let me elaborate more…


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Brett Jordan on Unsplash, adjusted by Ahmed Tarek

Moment of Truth


Do you remember the EmployeeManager class we discussed above, let’s say that we have it implemented differently.


Let’s assume that the code is as follows:


public class EmployeeManager
{
  public async Task<Employee> HireEmployee(Employee employee)
  {
    // Add Employee entry with basic info in DB
    // Assume that here we are actually doing the following:
    // 1. Open a connection to the database
    // 2. Build a SQL query to insert records in the database
    // 3. Execute the query
    // 4. Close connection
    
    // Add Emplyee to mailing groups
    // Assume that here we are actually doing the following:
    // 1. Open a connection to the mailing server
    // 3. Do whatever needed to add records to the mailing groups
    // 4. Close connection
    
    // Calculate tax scheme
    // Assume that here we are actually doing the following:
    // 1. Do the mathematical operations required
    
    // Update tax scheme in DB
    // Assume that here we are actually doing the following:
    // 1. Open a connection to the database
    // 2. Build a SQL query to insert records in the database
    // 3. Execute the query
    // 4. Close connection
    
    // Calculate holidays
    // Assume that here we are actually doing the following:
    // 1. Do the mathematical operations required
    
    // Update holidays in DB
    // Assume that here we are actually doing the following:
    // 1. Open a connection to the database
    // 2. Build a SQL query to insert records in the database
    // 3. Execute the query
    // 4. Close connection
  }
}

As we can notice here, the knowledge required is huge. The HireEmployee method needs to know too many things about the database structure, how to open/close connection, how to do the mathematical operations to calculate taxes and holidays, how to connect/disconnect to/from the mailing server, how to add to the mailing groups,…


This kind of enormous knowledge is a burden the HireEmployee method is carrying around.

If new changes are to be applied to the Employee database table or any of the related tables, the HireEmployee method would need to be updated and this means that the EmployeeManager class would accordingly be updated.


If new changes are to be applied to the mailing server, the HireEmployee method would need to be updated and this means that the EmployeeManager class would accordingly be updated.


And so on…


Eventually, we can conclude that the EmployeeManager class would have too many reasons for changing which is a clear indication that it violates the Single Responsibility Principle.


Now I can hear you saying:

Ok, now I get it but still I don’t know how to fix it. Is it possible?

Yes, for sure, it is possible. Let me show you…


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Brett Jordan on Unsplash, adjusted by Ahmed Tarek

Better Way


Again, back to the EmployeeManager class. If we want to implement it in the right way so that it abides by the Single Responsibility Principle, we can follow some steps as I show you.


First, we need to get back to the better -but not the best yet- version:


public class EmployeeManager
{
  private readonly EmployeeRepository _mEmployeeRepository;
  private readonly TaxCalculator _mTaxCalculator;
  private readonly MailingGroupsManager _mMailingGroupsManager;
  private readonly HolidaysCalculator _mHolidaysCalculator;

  public async Task<Employee> HireEmployee(Employee employee)
  {
    // Add Employee entry with basic info in DB
    var updatedEmployee = await _mEmployeeRepository.Add(employee);
    
    // Add Emplyee to mailing groups
    updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee);
    
    // Calculate tax scheme
    decimal tax = await _mTaxCalculator.Calculate(updatedEmployee);
    
    // Update tax scheme in DB
    updatedEmployee = await _mEmployeeRepository.UpdateTax(updatedEmployee, tax);
    
    // Calculate holidays
    byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee);
    
    // Update holidays in DB
    return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount);
  }
}

As we said before, the amount of knowledge required here is not as huge as it seems at the first glance.

However, is this the best we can do?

Actually no, we can do better as follows:


public class TaxManager
{
  private readonly EmployeeRepository _mEmployeeRepository;
  private readonly TaxCalculator _mTaxCalculator;

  public async Task<Employee> UpdateTax(Employee)
  {
    // Calculate tax scheme
    decimal tax = await _mTaxCalculator.Calculate(updatedEmployee);
    
    // Update tax scheme in DB
    return await _mEmployeeRepository.UpdateTax(updatedEmployee, tax);
  }
}

public class HolidaysManager
{
  private readonly EmployeeRepository _mEmployeeRepository;
  private readonly HolidaysCalculator _mHolidaysCalculator;

  public async Task<Employee> UpdateHolidays(Employee)
  {
    // Calculate holidays
    byte holidaysCount = await _mHolidaysCalculator.Calculate(updatedEmployee);
    
    // Update holidays in DB
    return await _mEmployeeRepository.UpdateHolidays(updatedEmployee, holidaysCount);
  }
}

public class EmployeeManager
{
  private readonly EmployeeRepository _mEmployeeRepository;
  private readonly TaxManager _mTaxManager;
  private readonly MailingGroupsManager _mMailingGroupsManager;
  private readonly HolidaysManager _mHolidaysManager;

  public async Task<Employee> HireEmployee(Employee employee)
  {
    // Add Employee entry with basic info in DB
    var updatedEmployee = await _mEmployeeRepository.Add(employee);
    
    // Add Emplyee to mailing groups
    updatedEmployee = await _mMailingGroupsManager.Add(updatedEmployee);
    
    // Update tax scheme
    updatedEmployee = await _mTaxManager.UpdateTax(updatedEmployee);
    
    // Update holidays
    return await _mHolidaysManager.UpdateHolidays(updatedEmployee);
  }
}

What we did here:

  • Abstracted the knowledge of calculating and adding the tax scheme into a separate TaxManager class.

  • Abstracted the knowledge of calculating and adding the holidays into a separate HolidaysManager class.


This means that the EmployeeManager class now knows less than before. Now, it doesn’t know that to update the tax scheme it needs to first use the TaxCalculator class and then use the EmployeeRespository class. The same applies to updating the holidays.


This leaves the EmployeeManager class with less knowledge and accordingly fewer reasons to change.


Now I can hear you saying:

Still, when a change comes in mandating that we need to add a new step, like adding the employee to the company social network for example, the EmployeeManager class would need to change.

Yes, I totally agree with you but I can’t see it as a problem.


This is something we can’t avoid as this is the core role of the EmployeeManager class; it handles the flow and steps to be done to hire an employee.


What we always need to keep in mind is that if the requirements change, some code would change. This is inevitable.


However, what we thrive to do is minimize the amount of the surrounding code to be changed or even touched.


 

Single Responsibility Principle SRP of SOLID Principles DotNet .NET CSharp C# Code Coding Programming Software Design Patterns Development Engineering Architecture Best Practice Knowledge Achievement Ahmed Tarek
Photo by Aaron Burden on Unsplash

Final Thoughts


In this article, we discussed the Single Responsibility Principle -which is also abbreviated as SRP- of the SOLID Principles.


When it comes to this principle, many developers get confused about what is much and less. That’s why this article made it clear once and ever.


Worth to mention, you need to keep in mind that in the software world and with the increasing speed of changes, there would always be some changes that would require a code change as well. This is normal, this is inevitable, and you should not fight it, you should adapt to it.


Finally, I hope you enjoyed reading this article as I enjoyed writing it.



Recent Posts

See All

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page