Published on

Single Responsibility Principle

5 min read - 909 words
Authors
  • avatar
    Name
    Cédric RIBALTA
    Twitter
post image

Introduction

The Single Responsibility Principle is the first SOLID principle.

It was formulated by Robert C. Martin, better known as Uncle Bob, in his book Clean Code.

This principle is often considered the simplest of the five SOLID principles, but it is also one of the most difficult to apply correctly.

In this article, we will explore this principle in detail, understand why it is so important, and how to apply it in practice.

What is the Single Responsibility Principle?

In his book, Uncle Bob defines responsibility as a reason to change. According to him, a class or module should have only one reason to change.

This goes beyond the simple idea that a class should do only one thing. He explains that changes in a class should be motivated by a single factor or a single group of people.

Key Difference with the Separation of Concerns Principle:

The Single Responsibility Principle is often confused with the Separation of Concerns Principle. Although these two principles are related, they are not identical.

What Martin emphasizes is the idea that a responsibility is related to a change axis.

For example, if a class manages both the user interface and the business logic, it could be changed due to a change in the user interface, even if the business logic remains the same.

This means that this class has multiple reasons to change, which violates the single responsibility principle.

Why is it important?

If a class manages both the business rules of a product and how that product is displayed in an interface. It may need to be modified if the display changes, even if the business rules do not change.

This creates a risk of introducing unintentional bugs in the business logic simply by changing the interface.

So, for Uncle Bob, it is not just about separating actions (like generating a report and sending it by email), but also about understanding who is likely to request a change and separating the code based on these potential change axes.

This approach makes the code more modular and easier to adapt when specific changes are needed.

Concrete Example:

Let's imagine a Employee class that would have the following three methods:

  • calculatePay(): This method calculates an employee's salary, based on hours worked, bonuses, etc. This is a responsibility related to the company's finances and payroll.
  • reportHours(): This method generates a report on the number of hours worked by the employee. This is a responsibility related to time management.
  • save(): This method saves the employee's information in a database. This is a responsibility related to data persistence.

Problem with this example:

This class violates the SRP principle because it fulfills several distinct roles, each of which can be modified for different reasons:

  • If the company changes its payroll policies, the calculatePay() method will need to be modified.
  • If the reporting requirements change, the reportHours() method will need to be modified.
  • If the database structure or format changes, the save() method will need to be modified.

Each method belongs to a different domain:

  • calculatePay(): belongs to the financial domain (payroll calculation).
  • reportHours(): concerns human resources or activity reporting.
  • save(): falls under the technical domain, i.e., data persistence in a database.

This results in multiple potential axes of change within the same class, which complicates maintenance and makes the code fragile.

A change in one domain could unintentionally affect other domains because everything is embedded in the same class.

Possible Solution :

To respect the Single Responsibility Principle, we could split the Employee class into three separate classes, each responsible for one of the three methods mentioned above.

  • A class to handle payroll calculation (EmployeePaymentCalculator or similar).
  • A class to manage time reporting (EmployeeTimeReporter or similar).
  • A class dedicated to data persistence (EmployeeDataSaver or similar).

This would ensure that each class has only one reason to change, simplifying maintenance and reducing the risk of bugs in other areas.

The downside of this solution is that you will have more classes to manage. An intermediate solution could be to use the "Facade" pattern.

The role of the Facade pattern :

The Facade Pattern is a structural design pattern that provides a simplified interface to interact with complex subsystems without exposing the details of their inner workings.

In other words, the facade serves as a single entry point that hides the complexity of several classes and simplifies access to them.

Applying the Facade Pattern in this Example :

Instead of having an Employee class that mixes multiple responsibilities, you could:

  1. Separate the responsibilities into specialized classes, such as:
  • EmployeePaymentCalculator: for calculating payroll,
  • EmployeeReportGenerator: for managing time reports,
  • EmployeeRepository: for saving data.
  1. Create a Facade class that encapsulates access to these different classes while providing a simple interface.

Here’s an example of what it could look like:

class EmployeeFacade {
  private paymentCalculator: EmployeePaymentCalculator
  private reportGenerator: EmployeeReportGenerator
  private repository: EmployeeRepository

  constructor() {
    this.paymentCalculator = new EmployeePaymentCalculator()
    this.reportGenerator = new EmployeeReportGenerator()
    this.repository = new EmployeeRepository()
  }

  calculatePay(employee: Employee): number {
    return this.paymentCalculator.calculatePay(employee)
  }

  generateReport(employee: Employee): string {
    return this.reportGenerator.generateReport(employee)
  }

  saveEmployee(employee: Employee): void {
    this.repository.save(employee)
  }
}

Thus, the EmployeeFacade class acts as a single entry point for all operations related to an employee while adhering to the single responsibility principle.

Conclusion :

The Single Responsibility Principle is a fundamental concept in object-oriented programming. It aims to make code more modular, easier to maintain, and less prone to bugs.

By following this principle, you will be able to design more coherent classes and modules that have only one reason to change. This will allow you to better manage the complexity of your code and make it more robust in the face of changes.