Single Responsibility Principle (SRP) is one of the most important object-oriented coding principle which, if followed religiously, can help you write a very good quality code. Here is what SRP states:
A block code representing class or method should have just one reason to change
In other words, Single Responsibility Principle can be stated as the following:
A block of code representing class or a method should do just one thing and do it very well
In yet another words, SRP can also be stated as the following:
A block of code representing class or method should have just one responsibility or serve just one functionality
Pictorially, decide for yourself which one of the following you would use for the knife (all in one tool or a single knife):
Noting some of the following would help you understand SRP vis-a-vis reusability
- For cutting vegetables, you would be using knife and not the all in one tool
- The all in one tool also serve functionality of scissors. Thus, change in the design of scissors may end of changing knife requirements. And, you would rather do not want that to happen. Thus, all in one tool is less reusable.
With above, it can be inferred that a class that follows single responsibility principle can said to be more reusable.
In this post, you will learn about some of the following:
- A Java Class code sample violating single responsibility principle
- A Java Method code example violating single responsibility principle
You may also want to check my another post on Liskov Substitution Principle using Java example.
A Java Class Code Sample violating Single Responsibility Principle
The class, UserService, has an API createUser, whose responsibility is to create a user given a User object. The following are more than one reason why the class UserService can change and thus, it violates single responsibility principle (SRP).
- Change in validation/business logic; This change could be brought due to change in business requirement and product manager can mandate this change.
- Change in information for database context; This change could be brought due to change in database details which would be mandatory as the code moves from from environment to other, thus, finally releasing in production. Ops staff will enforce this change.
- Change in database table design; This could be result of business requirement change and database staff could enforce this change.
public class UserService { private Logger logger = Logger.getLogger("com.training.core.srp.UserService"); public void createUser(User user) throws DBConnectionException { Connection connection = null; DataSource dataSource = null; // // Get the connection (reason to change) try { InitialContext initialContext = new InitialContext(); dataSource = (DataSource) initialContext.lookup("jdbc/tempDB"); } catch (NamingException e) { // Handle the exception using Logger } try { connection = dataSource.getConnection(); } catch (SQLException e) { logger.log(Level.SEVERE, "Issue connecting with database: " + e.getMessage()); throw new DBConnectionException(e.getMessage()); } // // Save the user (reason to change) // String sql = "INSERT INTO User (USER_ID, USERNAME) VALUES (?,?)"; try { PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, "some-user-id-123456"); pstmt.setString(2, "Ajitesh"); // execute insert SQL stetement pstmt.executeUpdate(); pstmt.close(); } catch (SQLException e) { logger.log(Level.WARNING, "Issue with saving the data: " + e.getMessage()); } } }
Given the fact that class, UserService, has got multiple reasons to change, which makes the class as non-cohesive, it can no longer be reused. That said, the class can be refactored appropriately to restore Single Responsibility Principle. The related code refactoring technique is also termed as Extract Class method.
Class Code Refactoring to Restore Single Responsibility Principle
The refactoring technique is Extract Class. The class UserService can be refactoried into following different classes to fix SRP violation:
- ConnectionHandler
- Extract database connection handling code in different class; Call it out as ConnectionHandler.
- UserDAO
- Extract data processing code in different class and call it out as UserDAO
- Have UserDAO use ConnectionHandler to get connection
- UserService
- Have UserService call a method on UserDAO instance
With above design followed, the refactored code of UserService would look like following:
public class UserService { private UserDAO userDAO; public void createUser(User user) throws UserCreationException { userDAO.save(user); } public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } }
With above refactoring, the following happens as a result of fixing SRP:
- Class UserService gets changed only due change in business logic.
- Any change in data logic would lead to change in UserDAO class
- Any change in connection handling would lead to change in ConnectionHandler class.
A Java Method Code Example violating Single Responsibility Principle
The method, createUser, in the UserService class, violates the Single Responsibility Principle. It can also be noted that the code smell such as Long Method also applies to the method, createUser. The method has got multiple reasons to change as mentioned in above section. The method can change due to change in some of the following:
- Change in validation logic
- Change in connection handling
- Change in database logic.
Method Code Refactoring to Restore Single Responsibility Principle
The recommended refactoring technique is Extract method. The createUser method can be refactored as following:
public void createUser(User user) throws DBConnectionException { saveUser(user); } private void saveUser(User user) throws DBConnectionException { // // Get the connection Connection connection = getConnection(); String sql = "INSERT INTO User (USER_ID, USERNAME) VALUES (?,?)"; try { PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, "some-user-id-123456"); pstmt.setString(2, "Ajitesh"); // execute insert SQL stetement pstmt.executeUpdate(); pstmt.close(); } catch (SQLException e) { logger.log(Level.WARNING, "Issue with saving the data: " + e.getMessage()); } } private Connection getConnection() throws DBConnectionException { Connection connection = null; DataSource dataSource = null; try { InitialContext initialContext = new InitialContext(); dataSource = (DataSource) initialContext.lookup("jdbc/tempDB"); } catch (NamingException e) { // Handle the exception using Logger } try {<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span> connection = dataSource.getConnection(); } catch (SQLException e) { logger.log(Level.SEVERE, "Issue connecting with database: " + e.getMessage()); throw new DBConnectionException(e.getMessage()); } return connection; }
Make a note of some of the following in refactored code:
- Each method has got one reason to change.
- Each method performs just one functionality or have single responsibility.
Sample Interview Questions
- What is Single Responsibility Principle (SRP)? Explain with example.
- What are some of the refactoring techniques which can be used to fix SRP violation issues?
- A class or a method violating SRP cann’t be said to be reusable? Explain Why?
Further Reading
Summary
In this post, you learned about some of the following:
- What is Single Responsibility Principle?
- Single Responsibility Principle Violations with Java Code Examples
- Sample interview questions
Did you find this article useful? Do you have any questions about this article or understanding the single responsibility principle using Java? Leave a comment and ask your questions and I shall do my best to address your queries.
- Agentic Reasoning Design Patterns in AI: Examples - October 18, 2024
- LLMs for Adaptive Learning & Personalized Education - October 8, 2024
- Sparse Mixture of Experts (MoE) Models: Examples - October 6, 2024
I found it very helpful. However the differences are not too understandable for me