The Single Responsibility Principle is one of the SOLID design principles. We can define it in the following ways,
- A reason to change
A class or method should have only one reason to change. - Single Responsibility
A class or method should have only a single responsibility.
Figure 1 - Single Responsibility Principle
SRP Benefits
The SRP has many benefits to improve code complexity and maintenance. Some benefits of SRP are following,
The SRP has many benefits to improve code complexity and maintenance. Some benefits of SRP are following,
- Reduction in complexity of a code
A code is based on its functionality. A method holds logic for a single functionality or task. So, it reduces the code complexity. - Increased readability, extensibility, and maintenance
As each method has a single functionality so it is easy to read and maintain. - Reusability and reduced error
As code separates based functionality so if the same functionality uses somewhere else in an application then don’t write it again. - Better testability
In the maintenance, when a functionality changes then we don’t need to test the entire model. - Reduced coupling
It reduced the dependency code. A method’s code doesn’t depend on other methods.
In this article for discussion and explanation purposes, I am introducing to you, two fictional characters Mark (a .NET developer) and Bob (a Tech Lead).
The story begins on the day when Bob contacts Mark and asks him to take up a role of an architect in his project called “Employee Management System”. We will see how Mark progresses his knowledge about Single Responsibility Principle day by day and how finally he come up with a great solution.
Intial Development
Bob (Tech Lead) explains requirements to Mark (a .Net developer). One of the primary requirements was to build a module which will have employee registration functionality.
How Mark Started
Mark defines a class named EmployeeService. It holds both employee data and registration operation as per following code snippet.
- namespace SRPApp
- {
- public class EmployeeService
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public void EmployeeRegistration(EmployeeService employee)
- {
- StaticData.Employees.Add(employee);
- }
- }
- }
Mark uses local storage to store data rather than database so he defines another class named StaticData. It has a property to store employee list. The following code snippet is for the same.
- using System.Collections.Generic;
- namespace SRPApp
- {
- public class StaticData
- {
- public static List<EmployeeService> Employees { get; set; } = new List<EmployeeService>();
- }
- }
To keep it simple, Mark chooses console application for UI interaction. It could be a web, windows or mobile application. The following code snippet is for user interaction. It’s an entry point for the application.
- using System;
- namespace SRPApp
- {
- 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, Mark demonstrated the same to Bob, and Bob appreciates the architecture. All things are looking good and working as per expectation such as
- He defines a separate class for employee’s functionality named EmployeeService rather than performing the operation in the UI/entry program.
- The method performs a registration process. Data stores in fields and pass via object rather than parameters.
- He defines a separate class for employee storage which is static.
But, it didn't end the application. He added new requirements in this registration process. He said,
- We need to store employee email address with existing fields.
- When an employee registers then he/she gets an email.
Bob requested Mark to implement these changes in this existing application.
Problem with Preceding Design
There are two requirements. Both requirements have changed, those are totally different. One is changing the data; whereas another one is impacting the functionality. Hence, we have two different types of reason to change a single class. So, it violates the SRP Principle.
Mark realized that he should develop code in this way that data and functionality can be separated.
Improve Development
Mark has an understanding of the requirements. He knows very well where he needs to change in the existing code so he doesn't have to worry about it. But he needs to implement these changes without violating SRP. In essence, he knows that there should be two classes one for employee data and another for employee functionality. So, both changes will be implemented in two classes.
Mark starts application development as per the new requirements. He defines a new class named Employee for data as per following code snippet.
- namespace SRPApp
- {
- public class Employee
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public string Email { get; set; }
- }
- }
As he uses local storage to store data so he updates StaticData class as well. The following code snippet is for the same
- using System.Collections.Generic;
- namespace SRPApp
- {
- public class StaticData
- {
- public static List<Employee> Employees { get; set; } = new List<Employee>();
- }
- }
Mark defines registration and mail sending functionality in the EmployeeService class. This class holds two methods, one for registration of employee while another method is for sending mail to the employee. Each method defines as per functionality so that these don’t violate SRP. The following code snippet is for the same.
- using MailKit.Net.Smtp;
- using MailKit.Security;
- using MimeKit;
- using System.Threading.Tasks;
- namespace SRPApp
- {
- public class EmployeeService
- {
- public async Task EmployeeRegistration(Employee employee)
- {
- StaticData.Employees.Add(employee);
- await SendEmailAsync(employee.Email, "Registration", "Congratulation ! Your are successfully 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);
- }
- }
- }
- }
The preceding code is developed in .NET Core, that’s why .NET Core MailKit Nuget package is used to send an email. This email send code is used for demonstration purpose. It will be worked with valid credentials and domain.
As Mark uses same console application for UI interaction so he updates it as per following code snippet.
- using System;
- namespace SRPApp
- {
- 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 was ready, Mark demonstrated the same to Bob, and Bob again appreciates the architecture. All things are looking good and working as per expectation such 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.
The preceding code is working perfectly but it is still violating the Single Responsibility Principle. The EmployeeService class holds two methods ,one for registration of employee while another method is for sending mail to the employee. The email sending functionality is not related to the employee entity directly. The email send and employee registration are totally different functionalities. If we change email sending process such as change provider, add attachment and use SSL etc then EmplyeeService class should not be changed but it isn't possible in preceding code.
Bob explained to Mark that a class should have only reason to change and have a single responsibility. The EmployeeService class has two responsibilities so it violates the SRP principle. It doesn’t mean that a class can’t have more than one method. A class can have multiple methods but these 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.. Mark understood that what he did wrong here and how violates the SRP principle. Now, he knows about the Single Responsibility Principle. He starts to update in existing code for SRP.
Final Development
Mark has an understanding that a class should have a single responsibility. He knows very well where he needs to change in the existing code. But he needs to implement these changes without violating SRP.
Mark knows that he needn’t build the entire application. He needs to change in the email sending functionality. He needs to make it independently so that it could be used as another place in the application. So, there are two changes in the application. Email sending code should be removed from EmployeeService class. He needs to define a new class for email sending functionality.
He defines a new class named EmailService to send email as per following code snippet.
- using MailKit.Net.Smtp;
- using MailKit.Security;
- using MimeKit;
- using System.Threading.Tasks;
- namespace SRPApp
- {
- public class EmailService
- {
- public 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 = "paathshaala.com";
- await smtpClient.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
- await smtpClient.SendAsync(emailMessage).ConfigureAwait(false);
- await smtpClient.DisconnectAsync(true).ConfigureAwait(false);
- }
- }
- }
- }
Mark uses the preceding code to send an email and call it in EmployeeService as per the following code snippet.
- using System.Threading.Tasks;
- namespace SRPApp
- {
- 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.");
- }
- }
- }
There is no change in the rest of the application.
Demonstration
Mark demonstrated the same to Bob. Now, it follows the Single Responsibility Principle. As he defined classes based on responsibilities and method based on individual functionality such
- He defines separate classes for employee data, functionality, email sending, UI interaction and data storage.
- Each class has a single responsibility and there is only one reason to change a class and method.
Now, Mark has implemented the Single Responsibility Principle in the application using C#. Bob seems happy because all his requirements seem satisfied and Mark is also happy because now he has become champ in the Single Responsibility Principle.
Conclusion
The SRP principle states that if we have two reasons to change for a class, we have to split the functionality into two classes. Each class will handle only one responsibility and in the future, if we need to make one more change we are going to make it in the new class which handles it.
If we put more than one functionality in one class then it introduces coupling between two functionalities. So, if we change one functionality there is a chance we broke coupled functionality, which requires another round of testing to avoid any bug in the production environment.
It reduces bug fixes and testing time once an application goes into the maintenance phase. It follows the DRY principle.
Please click on this link to view original articleClick on the link to view original article
Comments
Post a Comment