SOLID — SRP Single responsibility principle explanation

Sameera De Silva
4 min readNov 18, 2024

--

Java Single Responsibility Principle (SRP)

The Single Responsibility Principle is one of the five principles of object-oriented design, known as SOLID principles.

“A class should have only one reason to change.”

This means that a class should have one and only one responsibility or role in an application.

By adhering to SRP, we keep our classes focused and modular, making them easier to understand, maintain, and extend.

Common misconceptions of SRP

SRP means one method per class: Wrong

A class can have many methods, as long as all those methods contribute to the same single responsibility of the class.
For example, a User class might have methods like getName(), setEmail(), isValid(), etc., all related to user data and behavior.

SRP applies only to classes: Wrong

While SRP is often applied to classes, it can be applied to other software constructs like modules, packages, and services.
The principle is to keep each unit of code focused on a single responsibility.

Too many classes complicate the code: Wrong

While following SRP does lead to more classes, they are easier to maintain and reuse because of their clarity.

Advantages

Improved Maintainability
Changes in one responsibility of the class won’t affect other unrelated responsibilities.
Enhanced Readability
Small, focused classes are easier to read and understand.
Testability
It’s easier to write unit tests for a class that focuses on a single responsibility.
Reusability
Classes with a single responsibility can be reused in other projects or parts of the code.
Reduced Coupling
By separating responsibilities, you reduce the dependency between unrelated parts of the application.

Disadvantages

More Classes
Following SRP often results in more classes, which can seem overwhelming in larger projects.
Overhead in Smaller Projects
SRP may seem like overkill for simple applications or prototypes.

When implementing SRP, there is a high chance that you might violate another SOLID pricniple called, Dependency Inversion Principle (DIP),

Non-SRP Implementation

// Class violating SRP
public class EmployeeRegistration {
// Register an employee
public void registerEmployee(String name, String department) {
System.out.println("Employee " + name + " registered in " + department + ".");
}

// Calculate salary (non-related responsibility)
public double calculateSalary(double basic, double bonus) {
return basic + bonus;
}

// Check leave balance (non-related responsibility)
public int checkLeaveBalance(int employeeId) {
return 10; // Example leave balance
}
}

Issues:

The EmployeeRegistration class handles multiple responsibilities:

Registering an employee
Calculating salary
Checking leave balance

If the logic for one responsibility changes it may affect unrelated parts of the class.

SRP-Compliant Implementation but violates dependency inversion principle

Apply SRP (But Violates DIP)
Now responsibilities are split into EmployeeRegistration, SalaryCalculator, and LeaveManager. However, high-level classes depend on low-level implementations, violating DIP.

// Class for employee registration (single responsibility)
public class EmployeeRegistration {
public void registerEmployee(String name, String department) {
System.out.println("Employee " + name + " registered in " + department + ".");
}
}

// Class for salary calculation
public class SalaryCalculator {
public double calculateSalary(double basic, double bonus) {
return basic + bonus;
}
}

// Class for leave management
public class LeaveManager {
public int checkLeaveBalance(int employeeId) {
return 10; // Example leave balance
}
}

// High-level module tightly coupled to low-level modules (DIP Violation)
public class EmployeeService {
private SalaryCalculator salaryCalculator = new SalaryCalculator(); // Direct dependency
private LeaveManager leaveManager = new LeaveManager(); // Direct dependency

public void performEmployeeTasks(String name, String department, double basic, double bonus, int employeeId) {
new EmployeeRegistration().registerEmployee(name, department);
System.out.println("Salary: $" + salaryCalculator.calculateSalary(basic, bonus));
System.out.println("Leave balance: " + leaveManager.checkLeaveBalance(employeeId));
}
}

Correct Code with SRP and DIP Compliance
We introduce abstractions (interfaces) for SalaryCalculator and LeaveManager. The high-level EmployeeService depends on abstractions, not implementations.

// Abstraction for Salary Calculation
public interface SalaryCalculator {
double calculateSalary(double basic, double bonus);
}

// Concrete implementation for Salary Calculation
public class BasicSalaryCalculator implements SalaryCalculator {
@Override
public double calculateSalary(double basic, double bonus) {
return basic + bonus;
}
}

// Abstraction for Leave Management
public interface LeaveManager {
int checkLeaveBalance(int employeeId);
}

// Concrete implementation for Leave Management
public class BasicLeaveManager implements LeaveManager {
@Override
public int checkLeaveBalance(int employeeId) {
return 10; // Example leave balance
}
}

// Employee Registration (single responsibility, no changes needed)
public class EmployeeRegistration {
public void registerEmployee(String name, String department) {
System.out.println("Employee " + name + " registered in " + department + ".");
}
}

// High-level module depending on abstractions (DIP Honored)
public class EmployeeService {
private SalaryCalculator salaryCalculator; // Depends on abstraction
private LeaveManager leaveManager; // Depends on abstraction

// Constructor injection for flexibility
public EmployeeService(SalaryCalculator salaryCalculator, LeaveManager leaveManager) {
this.salaryCalculator = salaryCalculator;
this.leaveManager = leaveManager;
}

public void performEmployeeTasks(String name, String department, double basic, double bonus, int employeeId) {
new EmployeeRegistration().registerEmployee(name, department);
System.out.println("Salary: $" + salaryCalculator.calculateSalary(basic, bonus));
System.out.println("Leave balance: " + leaveManager.checkLeaveBalance(employeeId));
}
}

// Main class demonstrating SRP + DIP Compliance
public class Main {
public static void main(String[] args) {
// Use abstractions
SalaryCalculator salaryCalculator = new BasicSalaryCalculator();
LeaveManager leaveManager = new BasicLeaveManager();

// High-level module depends on abstractions
EmployeeService employeeService = new EmployeeService(salaryCalculator, leaveManager);

// Perform tasks
employeeService.performEmployeeTasks("John", "IT", 5000, 500, 101);
}
}

Explanation of “Depends on Abstraction”
What it means:
The EmployeeService class does not directly use a concrete class (e.g., BasicSalaryCalculator or BasicLeaveManager) to perform salary calculation or leave balance management.

Instead, it relies on interfaces (SalaryCalculator and LeaveManager) to define what behaviors (methods) are expected.

This abstraction decouples the EmployeeService class from specific implementations, making it easier to change or extend the behavior without modifying the EmployeeService code.

Why it matters:

By programming to an interface (abstraction), the code becomes:
Flexible: Different implementations can be plugged in as needed (e.g., HourlySalaryCalculator, PremiumLeaveManager).

Testable: Mock or stub implementations can be created for unit tests.

Future-proof: Changes to the specific implementation (e.g., a new way to calculate salary) don’t affect the high-level logic in EmployeeService.

This is a core concept in adhering to the Dependency Inversion Principle (DIP), which is part of the SOLID principles of software design.

--

--

No responses yet