Single Responsibility Principle – SOLID
Single Responsibility Principle: It is one of the SOLID design principle, which defines: Single reason to change: Each class or method should always have one reason to change. Single responsibility: Each class or method should always carry only a single responsibility.
If we have a class which carries more than one responsibility, then there are also more reason to change the class.
Benefits of Single Responsibility Principle
There are many benefits of using SRP in our project as it reduces the code complexity and application maintenance cost. Some major benefits of using Single Responsibility Principle are given below:
- Reusability and reduced error.
- Reduction in code complexity.
- Increased readability, maintainability & extensibility.
- Better testability.
- Reduced Coupling.
Reusability and reduced error: Defining method and class based on its functionality, reduces the complexity of code and we can use the same method for the matching logic throughout the scope of the application without rewriting the same code.
Reduction in code complexity: Having a separate class and method for each functionality makes the code easy to read and also reduces the number of lines of code which indirectly reduces the code complexity.
Increased readability, maintainability & extensibility: Writing reusable code in our application reduces the code complexity and also it makes easy to maintain the application code and structure which also help in reducing the maintainability cost.
Better testability: Having a separate module (class and method) for each functionality makes easier for tester to test the application as they only need to test required modules rather than testing the whole application.
Reduced coupling: Keeping the class and method separate based on functionality reduces the code dependency. Hence a method’s code doesn’t depend on another methods.
Example
Let’s try to elaborate the concept with a detailed example. For making the concept easy to understand I’m taking two character here. Peter (manager), John (a .Net developer). Peter came to John and discussed one requirement to build a module which will have employee registration functionality.
namespace SRPApp { public class EmployeeService { public string FirstName { get; set; } public string LastName { get; set; } public void EmployeeRegistration(EmployeeService employee) { StaticData.Employees.Add(employee); } } public class StaticData { public static List Employees { get; set; } = new List(); } class Program { static void Main(string[] args) { EmployeeService employeeService = new EmployeeService { FirstName = "John", LastName = "Deo" }; employeeService.EmployeeRegistration(employeeService); Console.ReadKey(); } } }
Since the module was ready, John presented the same to Peter. He appreciated the John for the architecture, everything was working as per expectation and looking fine.
- He defined the separate class for employee’s functionality and names EmployeeService rather than performing the operation in the UI/entry program.
- He also defined the separate class for storage i.e. static.
- As the module performs registration process. Data stores in the fields and pass via objects instead of parameters.
After couple of days, Peter again came to John with new requirements in the registration process. New requirement was:
- Need to store employee email address with existing fields.
- After the successful registration of an employee, he/she get an email.
Problem with the current design:
We’ve two requirements which are totally different first one is changing the data and the second one is changing the functionality of our module. If we observer carefully, we have now two different types of reason to change the single class. It means it violates the SRP principle.
John looked into this matter seriously and came up with another solution:
namespace SRPApp { public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } public class StaticData { public static List Employees { get; set; } = new List(); } public class EmployeeService { public async Task EmployeeRegistration(Employee employee) { StaticData.Employees.Add(employee); await SendEmailAsync(employee.Email, "Registration", "Congratulation ! Your are registered."); } private async Task SendEmailAsync(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("Mark Adam", "madam@sample.com")); emailMessage.To.Add(new MailboxAddress(string.Empty, email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart("plain") { Text = message }; using (SmtpClient smtpClient = new SmtpClient()) { smtpClient.LocalDomain = "sample.com"; await smtpClient.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None) .ConfigureAwait(false); await smtpClient.SendAsync(emailMessage).ConfigureAwait(false); await smtpClient.DisconnectAsync(true).ConfigureAwait(false); } } } // Main class class Program { static void Main(string[] args) { Employee employee = new Employee { FirstName = "John", LastName = "Deo", Email = "jdeo@sample.com" }; EmployeeService employeeService = new EmployeeService(); employeeService.EmployeeRegistration(employee).Wait(); Console.ReadKey(); } } }
Since the module is ready, John again presented this to Peter. Peter appreciated alot as everything was looking fine and as per expectation as:
- He defines separate classes for both employee data and functionality.
- The methods are separated based on functionalities.
- He defines a separate class for employee storage which is static.
Since the code is fine, but it still violates the SRP. The EmployeeService class holds two methods. One method is used for employee registration and another one is used for sending email. Email sending functionality is not directly related to the employee entity. Employee registration and sending email to employee are totally different functionalities. Let’s say in future, if we have to change provider, add attachments and use SSL etc then EmlpoyeeService class should not be changed but it is not possible in current code.
As per SRP a class should always have only one reason to change and have single responsibility. The current EmployeeService class has two responsibilities so it is violating the SRP. It doesn’t mean that a class can’t have more than one method. A class can have multiple methods but those are related to one entity of the application. In other words, EmployeeService class can have multiple methods related to the Employee entity such as registration, update and delete etc.
After observing all this, John did some changes and comes up with the final solution:
namespace SRPApp { public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } public class StaticData { public static List Employees { get; set; } = new List(); } public class EmployeeService { public async Task EmployeeRegistration(Employee employee) { StaticData.Employees.Add(employee); EmailService emailService = new EmailService(); await emailService.SendEmailAsync(employee.Email, "Registration", "Congratulation ! Your are successfully registered."); } } public class EmailService { private async Task SendEmailAsync(string email, string subject, string message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress("Mark Adam", "madam@sample.com")); emailMessage.To.Add(new MailboxAddress(string.Empty, email)); emailMessage.Subject = subject; emailMessage.Body = new TextPart("plain") { Text = message }; using (SmtpClient smtpClient = new SmtpClient()) { smtpClient.LocalDomain = "sample.com"; await smtpClient.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false); await smtpClient.SendAsync(emailMessage).ConfigureAwait(false); await smtpClient.DisconnectAsync(true).ConfigureAwait(false); } } } // Main class class Program { static void Main(string[] args) { Employee employee = new Employee { FirstName = "John", LastName = "Deo", Email = "jdeo@sample.com" }; EmployeeService employeeService = new EmployeeService(); employeeService.EmployeeRegistration(employee).Wait(); Console.ReadKey(); } } }