Single Responsibility Principle#

SRP

The Single Responsibility Principle is the first of the SOLID principles.

tl;dr - A class should have only one reason to change.

It states that a class should have only one reason to change. By this very reason it implies that is should have only one job or responsibility. High cohesion is a key aspect of SRP.

It seems easy to understand right? Is it though? We often see classes with more than one responsibility. We often see classes that change for more than one reason. We often see classes that have more than one job.

SRP is one of the more important concept in OO design. It’s also one of the simpler concepts to understand and adhere to. Yet oddly, SRP is often the most abused class design principle.”

― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Violation of SRP - Example 1#
 class User:
   def __init__(self, name, email):
      self.name = name
      self.email = email

   def send_email(self, message):
      # send email to self.email

   def save(self):
      # save user to database

   def __str__(self):
      return self.name

In this example, the User class has two responsibilities. It is responsible for sending emails and for saving the user to the database. If we need to change the way we save the user to the database, we will have to change the User class. If we need to change the way we send emails, we will have to change the User class. This is a violation of SRP.

Improving the code - Example 1#
 class User:
     def __init__(self, name, email):
         self.name = name
         self.email = email

     def __str__(self):
         return self.name

 class EmailSender:
     def send_email(self, user, message):
         # send email to user.email

 class UserRepository:
     def save(self, user):
         # save user to database

     def read(self, user_id):
         # read user from database

By separating the responsibilities into distinct classes, we adhere to the SRP. The User class now focuses solely on representing user data, while the other classes handle specific tasks such as persistence and email sending.

Violation of SRP - Example 2#
public class FileManager {
    public void readFile(String fileName) {
        // Code for reading a file
    }

    public void writeFile(String fileName, String content) {
        // Code for writing to a file
    }

    public void encryptFile(String fileName) {
        // Code for encrypting a file
    }

    public void compressFile(String fileName) {
        // Code for compressing a file
    }
}

In this example, the FileManager class violates the SRP because it handles file operations, encryption, and compression. To fix this, we should separate these responsibilities into separate classes. Note though how easy it is to violate the SRP? We often see classes like this in the wild and we probably have written many of them ourselves.

Improving the code - Example 2#
public class FileManager {
    public void readFile(String fileName) {
        // Code for reading a file
    }

    public void writeFile(String fileName, String content) {
        // Code for writing to a file
    }
}

public class FileEncryptor {
    public void encryptFile(String fileName) {
        // Code for encrypting a file
    }
}

public class FileCompressor {
    public void compressFile(String fileName) {
        // Code for compressing a file
    }
}

Now the responsibilities are separated, and each class has a single responsibility. The FileManager class focuses on file operations, the FileEncryptor class handles encryption, and the FileCompressor class deals with file compression.

By adhering to the SRP, we improve the maintainability, testability, and re-usability of our code. Each class becomes more focused and easier to understand, modify, and extend.