Back to Overview

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

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 reports
  • FileManager only handles file operations
  • EmailService 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.

// Output will appear here