When writing code, a programmer should try to write clean code. What does it mean? Among other things, this means that the code should comply with design patterns and good practices. The most popular design rules in object-oriented languages are probably those proposed by Robert C. Martin, which were called SOLID. Most developers know what this term means, but do they use it in practice? Not entirely, in this article I will try to describe the first of the SOLID principles, it will be SRP, i.e. Single Responsibility Principle.
SOLID - SRP - Everything You Should Know About the Single Responsibility Principle
SRP is the principle of single responsibility. It states that each class should have only one responsibility. This means that if a class has more than one responsibility, then there is more than one reason to change it in the future. And there should never be more than one reason to modify a class. The SRP rule primarily draws attention to separating code fragments that depend on different "actors". If a class is responsible for more than one area in our project, this can often be a problem because in the future, by making changes to one area, it may break something in another area that may seem unrelated to it. The single responsibility rule applies not only to classes or interfaces (because we should focus on them), but also to modules and methods. Classes and methods should focus on one specific thing and necessarily do that thing well. A class should have one functionality that it implements, i.e. one responsibility just mentioned. It will be best to illustrate this with an example.
Code not following the single responsibility principle:
public interface IEmailSender
{
void Send();
void LogError(Exception exception);
void AddToStats();
}
public class EmailSender : IEmailSender
{
public void Send()
{
throw new NotImplementedException();
}
public void AddToStats()
{
throw new NotImplementedException();
}
public void LogError(Exception exception)
{
throw new NotImplementedException();
}
}
For the simplicity of the example, the methods are not implemented, and I did not add any properties. To present what is most important in this article, i.e. the single responsibility rule, we do not need to implement these methods. In the above example, you can see that the EmailSender class has more than one responsibility. This means that there is more than one reason to change this class in the future. First of all, this class is responsible for sending e-mails, and is also responsible for adding error information to the statistics and logging. There are far too many reasons to change. If a class sends an e-mail, it should do only that, and certainly not log errors or keep statistics. This example clearly violates the principle of single responsibility. Okay, so what should this code look like, applying the single responsibility principle?
First of all, you need to divide each responsibility into a separate class. So in this case, you need to create 3 classes, they will be:
-class for sending emails,
-error logging class,
-class to keep statistics.
Code #1 applying the single responsibility principle:
public interface IEmailSender
{
void Send();
}
public interface ILogger
{
void LogError(Exception exception);
}
public interface IStatistics
{
void Add(IEmailSender emailSender);
}
public class EmailSender : IEmailSender
{
public void Send()
{
throw new NotImplementedException();
}
}
public class Logger : ILogger
{
public void LogError(Exception exception)
{
throw new NotImplementedException();
}
}
public class Statistics : IStatistics
{
public void Add(IEmailSender emailSender)
{
throw new NotImplementedException();
}
}
public class Program
{
private IEmailSender _emailSender;
private ILogger _logger;
private IStatistics _statistics;
public Program(IEmailSender emailSender, ILogger logger, IStatistics statistics)
{
_emailSender = emailSender;
_logger = logger;
_statistics = statistics;
}
public void SendEmail()
{
try
{
_emailSender.Send();
_statistics.Add(_emailSender);
}
catch (Exception exceptpion)
{
_logger.LogError(exceptpion);
throw;
}
}
}
As you can see, the current code has been divided into more classes and each of them has only one responsibility, that is, one reason to change. The classes are smaller, which makes it easier to read and any future changes will be much easier. Each class has a specific function it performs. Class for sending emails - only sends emails. The logging class - logs errors, and the statistics class - adds some details to the statistics.
You don't want your classes to be like a penknife, meaning they have a lot of different functions. You want your class to be like a knife or scissors and be responsible for this one specific function. It's usually the case that if something is good for everything, it really sucks.
So should classes only have one method?
In the example above, the classes have only one method, but this is not necessarily always the case. The idea is that these methods are specific to a given object. The point is not that classes should have one field or method, but that they should be responsible for one specific thing, for one activity. I will demonstrate this with one more example.
Code #2 applying the single responsibility principle:
public interface IEmployee
{
void SetFirstName(string firstName);
void SetLastName(string lastName);
decimal GetSalary();
}
In the above example, the IEmployee interface may have SetFirstName, SetLastName or GetSalary methods, but should not have, for example, a Save method. Because if, for example, the way employee data is recorded changes, this will be another reason to change it.
So then the class implementing this interface would have two reasons to change:
-Changing the way data is saved,
-Adding a new property (e.g. email address) to the employee.
You need to separate the Save method into a separate class that will have the SaveEmployee method. Then each of our newly created classes will have its own individual responsibilities. You must remember that if two areas are connected, changes to one of them may also destroy the functioning of the other. When a change is required, it should only affect one class. The same applies to methods, because if, for example, one method performs a calculation and displays the result of the calculation, it also does not satisfy the single responsibility principle because it has more than one reason to change. The method of calculation and the way the result is displayed may change.
Advantages of the single responsibility principle:
By applying the single responsibility principle, we ensure better code readability and also minimize the risk of future problems when modifying such a class. This means that our code is easier to maintain and constantly develop. If we want to introduce one change, we do not have to make changes in several classes, just one is enough.
SUMMARY
In today's article, I tried to explain to you how to understand the first principle of SOLID, namely S for Single Responsibility Principle. I hope that thanks to the examples presented, you will learn this knowledge even better. The SRP principle seems quite simple at first glance, but in reality it is difficult to implement and programmers often have problems with it. I think that with experience you will understand this principle better and better. And thanks to the single responsibility principle, your code will be easier to modify and maintain. In subsequent articles I will describe the remaining SOLID rules.