Single Responsibility Principle

The single responsibility principle (SRP) states that a software component (in general, a class) must have only one responsibility. The fact that the class has a sole responsibility means that it is in charge of doing just one concrete thing, and as a consequence of that, we can conclude that there must be only one reason to change it. It is one of the famous 5 SOLID principles.

A class should have only one reason to change.

1. Motivation

If we have to make modifications to a class, for different reasons, it means the abstraction is incorrect, and that the class has too many responsibilities.

Basically, we want to avoid, in all cases having objects with multiple responsibilities (often called god-objects, because they know too much, or do more than they should). These objects group different (mostly unrelated) behaviors, thus making them harder to maintain.

There is another way of looking at the single responsibility principle. If, when looking at a class, we find methods that are mutually exclusive and do not relate to each other, it means that these methods have different responsibilities that should have to be broken down into different smaller classes. Remember that the smaller classes are always better to program with and test.

2. Single Responsibility Principle in Action

To understand the single responsibility principle in the real world, we can look at the JDK code and other popular libraries such as Log4J2, Spring framework etc.

Log4J2 code has different classes with logging methods, different classes are logging levels and so on. Similarly, in the Spring framework, classes are really very small and usually perform only one or two related actions.

Let’s take another example of our own to understand better what is single responsibility principle.

2.1. Problem

To understand the SRP principle, let’s assume we have to work on an application that involves working with employees. We have an interface IEmployeeStore and its implementation EmployeeStore which have the following methods.

public interface IEmployeeStore {

	public Employee getEmployeeById(Long id);

	public void addEmployee(Employee employee);

	public void sendEmail(Employee employee, String content);
}
public class EmployeeStore implements IEmployeeStore {

	@Override
	public Employee getEmployeeById(Long id) {
		return null;
	}

	@Override
	public void addEmployee(Employee employee) {

	}

	@Override
	public void sendEmail(Employee employee, String content) {
	}
}

The above class seems good on any normal application. using EmployeeStore, can get/add employees and send emails to them.

Now suppose after product release, we got a requirement that email content can be of two types i.e. HTML and text. Above class supports only text content. What will you do?

One way to solve this issue is to create another method sendHtmlEmail() – but what happens when we are asked to support different protocols for sending emails for both content types? Overall the class will look very ugly and difficult to read and maintain.

And there is always a chance that during modification, some developers can change the logic used for get/add employee methods if they are shared.

2.2. Implementing SRP Principle

To solve this issue, we must pull out the email capability to separate interfaces and classes which specifically handle only email-related functionality. This way, we are sure that other features are not impacted.

Based on our assumptions, we can abstract out two interfaces IEmailSender and IEmailContent. The first interface is responsible for the email-sending process, and the second interface passes the email content and its type.

The refactored classes are given below.

public interface IEmployeeStore {

	public Employee getEmployeeById(Long id);
	public void addEmployee(Employee employee);
}

public class EmployeeStore implements IEmployeeStore {

	//inject in runtime
	private IEmailSender emailSender;

	@Override
	public Employee getEmployeeById(Long id) {
		return null;
	}

	@Override
	public void addEmployee(Employee employee) {
	}
}
public interface IEmailSender {

	public void sendEmail(Employee employee, IEmailContent content);
}

public class EmailSender implements IEmailSender {

	@Override
	public void sendEmail(Employee employee, IEmailContent content) {
		//logic
	}
}
public interface IEmailContent {
}

public class EmailContent implements IEmailContent {

	private String type;
	private String content;
}

Now, if we want to change the email functionality, we will change EmailSender class only. Any change to employee CRUD operations will happen in EmployeeStore only. Any change in one capability will not change another one by mistake. They are now easy to read and maintain as well.

3. Advantages

3.1. Easy to understand and maintain

When the class only does “one thing”, its interface usually has a smaller number of methods (and member variables) that are fairly self-explanatory. It makes the code easier to read and understand.

When there is a need to change the application behavior, changes related to the class’s responsibility are fairly isolated. It reduces the chance of breaking other unrelated areas of the software. It makes the code easier to maintain.

3.2. Improved usability

If a class has multiple responsibilities and it is required to be used in other parts of the application for using a certain responsibility, it may adversely expose other responsibilities which may not be desired. It can lead to undesired behavior in the application e.g. security and data privacy issues.

If the class follows the SRP principle strictly then unnecessary functionality will not be exposed and it makes the class to be more usable without fearing adverse effects.

4. Conclusion

The single responsibility principle design pattern has a hugely positive impact on the adaptability of code. Compared to equivalent code that does not adhere to the principle, SRP-compliant code leads to a greater number of classes that are smaller and more directed in scope.

The SRP is primarily achieved through abstracting code behind interfaces and delegating responsibility for unrelated functionality to whichever implementation happens to be behind the interface at run time.

Happy Learning !!

Comments

Subscribe
Notify of
guest
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode