A class should have only one reason to change.
It means a class should have only one task to perform.
This means that if a class is a data container, like a Book class or a Student class, and it has some fields regarding that entity, it should change only when we change the data model.
Importance of SRP:
- Many different teams can work on the same project and edit the same class for different reasons, this could lead to incompatible modules.
- It makes version control easier. For example, say we have a persistence class that handles database operations, and we see a change in that file in the GitHub commits.
- Merge conflicts are another example. They appear when different teams change the same file. But if the SRP is followed, fewer conflicts will appear – files will have a single reason to change, and conflicts that do exist will be easier to resolve.
Example while SRP is importance
We will take the example of bookstore invoice program. Where we did some common mistakes and that violate the Single Responsibility Principle.
This is a simple book class with some fields.
class Book {
private:
String name;
String authorName;
int price;
public:
Book(String name, String authorName, int price) {
this.name = name;
this.authorName = authorName;
this.price = price;
}
}
This is a invoice class which will contain the logic for creating the invoice and calculating the total price.
public class Invoice {
private:
Book book;
int quantity;
double total;
public:
Invoice(Book book, int quantity) {
this.book = book;
this.quantity = quantity;
this.total = this.calculateTotal();
}
double calculateTotal() {
double price = (book.price * this.quantity);
return price;
}
void printInvoice() {
cout << quantity << "x " << book.name << " " book.price << "$";
System.out.println("Total: " + total);
}
void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}
}
This invoice class contains:
- calculateTotal method, which calculates the total price
- printInvoice method, that should print the invoice to console
- saveToFile method, responsible for writing the invoice to a file
This class violates the Single Responsibility Principle in multiple ways.
- First violation : printInvoice method, which contains our printing logic. The SRP states that our class should only have a single reason to change, and that reason should be a change in the invoice calculation for our class.
- Next violation : saveToFile method. It is also an extremely common mistake to mix persistence logic with business logic. Currently this function is writing to a file. But in future, it could be saving to a database, making an API call, or other stuff related to persistence.
To fix these violation :
We can create new classes for our printing and persistence logic so we will no longer need to modify the invoice class for those purposes.
We create 2 classes, InvoicePrinter and InvoicePersistence, and move the methods.
public class InvoicePrinter {
private:
Invoice invoice;
public:
InvoicePrinter(Invoice invoice) {
this.invoice = invoice;
}
void print() {
cout << invoice.quantity << "x " << invoice.book.name << " " << invoice.book.price << " $";
cout << "Total: " + invoice.total + " $";
}
}
public class InvoicePersistence {
Invoice invoice;
public:
InvoicePersistence(Invoice invoice) {
this.invoice = invoice;
}
void saveToFile(String filename) {
// Creates a file with given name and writes the invoice
}
}
Now our class structure obeys the Single Responsibility Principle and every class is responsible for one aspect of our application.