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.
Consider a simple example of an employee management system. We have an Employee class that handles:
This Employee class violates the SRP principle because it has multiple reasons to change:
Each of these tasks should belong to separate classes so that changes in one responsibility do not affect other unrelated parts of the system.
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;
}
}
The Employee class has multiple responsibilities:
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.
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:
// 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());
}
}