Single Responsibility Principle (SRP)


The Single Responsibility Principle (SRP) is one of the five S.O.L.I.D. design principles, and it focuses on the idea that a class should only have one responsibility or one reason to change.

A responsibility refers to a role or a purpose the class is supposed to fulfill. By adhering to SRP, classes become smaller, more cohesive, and easier to maintain or extend. This principle is vital for reducing code complexity and improving maintainability in object-oriented programming.

SRP Violation Example

Consider a simple example of an employee management system. We have an Employee class that handles:

  1. Storing employee information.
  2. Calculating the salary.
  3. Saving employee data to a database.

This Employee class violates the SRP principle because it has multiple reasons to change:

  1. If the salary calculation logic changes.
  2. If the way employee data is stored changes (e.g., moving from a SQL database to a NoSQL database).
  3. If new information is added to employee records.

Each of these tasks should belong to separate classes so that changes in one responsibility do not affect other unrelated parts of the system.

Code for SRP Violation

        class Employee {
            private String name;
            private String position;
            private double salary;
        
            public Employee(String name, String position, double salary) {
                this.name = name;
                this.position = position;
                this.salary = salary;
            }
        
            // This method handles salary calculation (responsibility 1)
            public double calculateSalary() {
                // Salary calculation logic
                return this.salary * 1.2;  // Assume some bonus calculation
            }
        
            // This method handles storing employee data to a database (responsibility 2)
            public void saveToDatabase() {
                // Save employee details to database logic
                System.out.println("Saving employee to database: " + name);
            }
        
            // This method handles employee data (responsibility 3)
            public String getEmployeeDetails() {
                return "Name: " + name + ", Position: " + position + ", Salary: $" + salary;
            }
        }
        
    

Why This Violates SRP:

The Employee class has multiple responsibilities:

  1. Salary Calculation (calculateSalary()).
  2. Persistence (saving employee to the database) (saveToDatabase()).
  3. Employee Data (getEmployeeDetails()).

Any changes to the salary calculation, the database system, or employee details could require modifying this class. This makes the class fragile, harder to test, and difficult to extend.

How to Achieve SRP

To adhere to SRP, we can refactor the code by separating concerns into different classes, each with a single responsibility.
Here's how we can do that:

  1. Employee class should only handle employee-related information.
  2. SalaryCalculator class should be responsible for salary calculation.
  3. EmployeeRepository class should be responsible for saving employee data to a database.

Refactored Code Following SRP:


// Class responsible for employee information (responsibility 1)
class Employee {
    private String name;
    private String position;
    private double salary;

    public Employee(String name, String position, double salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public String getPosition() {
        return position;
    }

    public double getSalary() {
        return salary;
    }
}

// Class responsible for salary calculation (responsibility 2)
class SalaryCalculator {
    public double calculate(Employee employee) {
        // Salary calculation logic
        return employee.getSalary() * 1.2;  // Assume some bonus calculation
    }
}

// Class responsible for saving employee data to the database (responsibility 3)
class EmployeeRepository {
    public void save(Employee employee) {
        // Save employee details to database logic
        System.out.println("Saving employee to database: " + employee.getName());
    }
}

// Demonstrating the usage of refactored classes
public class SRPExample {
    public static void main(String[] args) {
        Employee emp = new Employee("John Doe", "Developer", 50000);
        
        // Responsibility 1: Salary Calculation
        SalaryCalculator salaryCalculator = new SalaryCalculator();
        double salary = salaryCalculator.calculate(emp);
        System.out.println("Calculated Salary: $" + salary);
        
        // Responsibility 2: Saving employee data
        EmployeeRepository employeeRepository = new EmployeeRepository();
        employeeRepository.save(emp);
        
        // Responsibility 3: Employee data (handled by Employee class)
        System.out.println("Employee Details: " + emp.getName() + ", " + emp.getPosition() + ", $" + emp.getSalary());
    }
}

    
How This Adheres to SRP:
  1. The Employee class now only manages employee-related information like name, position, and salary, which is its sole responsibility.
  2. The SalaryCalculator class is solely responsible for salary calculation logic, adhering to its single responsibility.
  3. The EmployeeRepository class is responsible for saving employee data to the database, handling persistence logic independently of other classes.

Benefits of SRP: