This principle refers to the unique responsibility that each program should have with a specific and limited task.
The single responsibility principle (SRP) is a software design principle that states that each class should only change for one reason. This means that a class should only have one responsibility and that all of its methods should be related to that responsibility.
It is then intended that our program should do something determined and concrete.
Some of the benefits of this single liability principle:
- About to testing. It is simplified because a class has only one responsibility.
- Decreased coupling because lower functionality in a class will cause it to have fewer dependencies.
- The organization of classes and packages will be better and easier.
Let’s see below an example that does not follow this principle, and then we will modify it so that you follow it.
Poorly implemented code where we DO NOT use this single responsibility principle
This class UserLoginService has the responsibility to perform the login process, but also the responsibility of sending messages to the user.
// Single responsibility principle violation
class UserLoginService {
private final DataBase db;
UserLoginService(DataBase db) {
this.db = db;
}
User login(String userName, String password) {
User user = db.findUserByUserName(userName);
if (user == null) {
// do something
throw new IllegalArgumentException("Not found");
}
// login process..
return user;
}
// email responsibility should not be part of Login responsibility
void sendEmail(User user, String msg) {
// sendEmail email to user
}
}
We can observe better by running the program that this service has two different responsibilities and needs some change.
public class Demo {
public static void main(String[] args) {
UserLoginService userLoginService = new UserLoginService(new DataBase());
User user = userLoginService.login("Bart", "Simpson");
userLoginService.sendEmail(user, "Hi Bart");
}
}
This code violates the principle of single liability.
It’s doing two things for different purposes, so this class violates the SRP because it has two different responsibilities:
- Login
- Send Emails
If we need to change the way to send emails, we would also need to change the UserLoginService class that contains other functionality. As a result, the class becomes more difficult to maintain and less reusable.
How to implement the Single responsibility principle
We are going to fix the above code using this principle of single responsibility.
Each class should have the same set of common features
So what should we do?
It would be convenient to separate the class in two classes. One for login and another one for messaging functionality.
To follow the SRP whe are going to move sendMail method to another class which is responsible only for sending messages.
class MessageSenderService {
void sendEmail(User user, String msg) {
// sendEmail email to user
}
}
Refactor UserLoginService
class UserLoginService {
private final DataBase db;
UserLoginService(DataBase db) {
this.db = db;
}
User login(String userName, String password) {
User user = db.findUserByUserName(userName);
if (user == null) {
// do something
throw new IllegalArgumentException("Not found");
}
// login process..
return user;
}
}
Running our program:
public class Demo {
public static void main(String[] args) {
UserLoginService userLoginService = new UserLoginService(new DataBase());
User user = userLoginService.login("Bart", "Simpson");
MessageSenderService messageSenderService = new MessageSenderService();
messageSenderService.sendEmail(user, "Hi Bart");
}
}
Now, each class has a single responsibility. UserLoginService class represents the process to login the User, and MessageSenderService class handles email sending.
The UserLoginService and MessageSenderService classes are refactored to adhere to the SRP.
The UserLogin class now has a single responsibility to log in the User.
Send emails to the User has been delegated to a separate class called MessageSenderService to achieve this principle SRP.
Conclusion
SRP makes our code easier to maintain and understand, as well as more reusable, less coupled, and testable.
Every class or component that uses SRP has a distinct and well-defined responsibility. As a result, changes associated with a particular responsibility can be isolated without affecting functionality that is not related, making the code easier to understand, modify, and maintain.
It also becomes simpler to reuse each component in different areas of the codebase or in future projects because it is task-specific.
Writing unit tests for the code gets easier in terms of coverage.
You can review these examples at GitHub