Single Responsibility Principle#
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
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.
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.
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.
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.