Single Responsibility Principle (SRP)
The Single Responsibility Principle (SRP) states that a class should have only one reason to change. This means a class should have only one job or responsibility.
When a class has multiple responsibilities, it becomes coupled. A change to one responsibility may impair or inhibit the class's ability to meet the others. This kind of coupling leads to fragile designs that break in unexpected ways when changed.
Why SRP Matters
- Maintainability: Classes with a single responsibility are easier to maintain.
- Testing: Smaller, focused classes are easier to test.
- Lower coupling: Less functionality in a single class means fewer dependencies.
- Organization: Code is better organized and easier to navigate.
Bad Example: Violating SRP
Consider a ReportGenerator
class that both generates reports and handles email delivery:
class ReportGenerator {
constructor() {
// Initialize report generator
}
generateReport(data) {
// Logic to generate a report
console.log("Generating report with data:", data);
return `Report generated at ${new Date().toLocaleString()}`;
}
saveReportToFile(report, filename) {
// Logic to save the report to a file
console.log(`Saving report to ${filename}`);
}
sendReportByEmail(report, emailAddress) {
// Logic to send the report via email
console.log(`Connecting to email server...`);
console.log(`Sending report to ${emailAddress}`);
console.log(`Disconnecting from email server...`);
}
}
This class violates SRP because it has multiple responsibilities:
- Generating reports
- Saving reports to the file system
- Sending emails
If the email system changes, we need to modify this class, even though report generation logic hasn't changed. Similarly, changes to the report format would risk breaking the email functionality.
Good Example: Following SRP
Let's refactor the above example to follow SRP by separating the responsibilities:
// Responsibility: Generate reports
class ReportGenerator {
generateReport(data) {
console.log("Generating report with data:", data);
return `Report generated at ${new Date().toLocaleString()}`;
}
}
// Responsibility: Save files
class FileManager {
saveToFile(data, filename) {
console.log(`Saving data to ${filename}`);
}
}
// Responsibility: Send emails
class EmailService {
sendEmail(content, emailAddress) {
console.log(`Connecting to email server...`);
console.log(`Sending content to ${emailAddress}`);
console.log(`Disconnecting from email server...`);
}
}
// Usage example
class ReportingWorkflow {
constructor() {
this.reportGenerator = new ReportGenerator();
this.fileManager = new FileManager();
this.emailService = new EmailService();
}
generateAndSendReport(data, filename, emailAddress) {
const report = this.reportGenerator.generateReport(data);
this.fileManager.saveToFile(report, filename);
this.emailService.sendEmail(report, emailAddress);
}
}
Now each class has a single responsibility:
ReportGenerator
only generates reportsFileManager
only handles file operationsEmailService
only handles email operations
The ReportingWorkflow
class coordinates these single-responsibility classes to accomplish the overall task.
Interactive Demo: Task Manager
This demo shows how SRP can be applied to a task management system. Try adding tasks and see how different responsibilities are handled by separate components.