Profile Photo

Solid Principle: Single Responsibility

Created on: Nov 3, 2024

We can understand single responsibility with a sample example.

public class Square { private int size; public Square(int size) { this.size = size; } public int getSize() { return size; } public int area() { return size * size; } public int perimeter() { return 4 * size; } public void draw(Square square) { int size = square.getSize(); System.out.println("Drawing square of size " + size); for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { System.out.print("* "); } System.out.println(); } } public void rotate(Square square, int degrees) { System.out.println("Rotating square of size " + square.getSize() + " by " + degrees + " degrees."); } }

There are few problems with above code.

  1. Below code contains geometry and graphical related functionality. Mixing both this unrelated code make it low cohesive.
  2. Testing both functionality in isolation is not easy.
  3. If we are adding similar shape in future like Rectangle which uses same code for draw and rotate, you will have to duplicate the code.

The solution is single responsibility principle which states that every software component should have only and only one reason to change. Component can be class, method or a module. Single responsibility principle aims for high cohesion and loose coupling.

Note:

  • Cohesion: Cohesion is the degree to which the various parts of a software component are related.
  • Coupling: Coupling is defined as the lavel of interdependency between various software component.

In above code, there are two reasons for the code to change. Firstly geometry and secondly geometrical. To solve this problem, we can split the class into two parts. One takes care of geometry and another graphical representation.

public class Square { private int size; public Square(int size) { this.size = size; } public int getSize() { return size; } public int area() { return size * size; } public int perimeter() { return 4 * size; } } class SquareGraph{ public void draw(Square square) { int size = square.getSize(); System.out.println("Drawing square of size " + size); for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { System.out.print("* "); } System.out.println(); } } public void rotate(Square square, int degrees) { System.out.println("Rotating square of size " + square.getSize() + " by " + degrees + " degrees."); } }

This will promote high cohesion, ease of testing and flexibility.

Let's consider another example.

import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; class Employee { private String empId; private String name; private double salary; public Employee(String empId, String name, double salary) { this.empId = empId; this.name = name; this.salary = salary; } public String getName() { return name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public void save() { String jdbcUrl = "jdbc:mysql:"; String dbUser = "your_username"; String dbPassword = "your_password"; String insertSQL = "INSERT INTO employees (emp_id, name, salary) VALUES (?, ?, ?)"; try (Connection conn = DriverManager.getConnection(jdbcUrl, dbUser, dbPassword); PreparedStatement pstmt = conn.prepareStatement(insertSQL)) { pstmt.setString(1, empId); pstmt.setString(2, name); pstmt.setDouble(3, salary); int rowsInserted = pstmt.executeUpdate(); if (rowsInserted > 0) { System.out.println("Employee saved successfully!"); } } catch (SQLException e) { e.printStackTrace(); System.out.println("Error saving employee to the database."); } } public double calculateTax() { if (salary <= 50000) { return salary * 0.15; // 15% tax for salaries <= 50,000 } else if (salary <= 100000) { return salary * 0.20; // 20% tax for salaries between 50,001 and 100,000 } else { return salary * 0.25; // 25% tax for salaries > 100,000 } } }

In above code we have three reasons to change the code,

  1. Change in db from mysql to mongodb.
  2. change in tax calculation
  3. Change in employee information.

We could split above code in three parts adhering to solid principle.

package com.example; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; class Employee { private String empId; private String name; private double salary; public Employee(String empId, String name, double salary) { this.empId = empId; this.name = name; this.salary = salary; } public String getEmpId() { return empId; } public String getName() { return name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } } class EmployeeRepo { public void save(Employee employee) { String jdbcUrl = "jdbc:mysql:"; String dbUser = "your_username"; String dbPassword = "your_password"; String insertSQL = "INSERT INTO employees (emp_id, name, salary) VALUES (?, ?, ?)"; try (Connection conn = DriverManager.getConnection(jdbcUrl, dbUser, dbPassword); PreparedStatement pstmt = conn.prepareStatement(insertSQL)) { pstmt.setString(1, employee.getEmpId()); pstmt.setString(2, employee.getName()); pstmt.setDouble(3, employee.getSalary()); int rowsInserted = pstmt.executeUpdate(); if (rowsInserted > 0) { System.out.println("Employee saved successfully!"); } } catch (SQLException e) { e.printStackTrace(); System.out.println("Error saving employee to the database."); } } } class TaxCalculator { public double calculateTax(int salary) { if (salary <= 50000) { return salary * 0.15; // 15% tax for salaries <= 50,000 } else if (salary <= 100000) { return salary * 0.20; // 20% tax for salaries between 50,001 and 100,000 } else { return salary * 0.25; // 25% tax for salaries > 100,000 } } }

Benefits of the Single Responsibility Principle (SRP)

  1. Improved Code Readability and Maintainability
  2. Enhanced Testability
  3. Increased Reusability
  4. Reduced Risk of Introducing Bugs