Design Principle: Single Responsibility Principle(SRP)

Summary

Principle NameSingle Responsibility Principle
AcronymSRP
Other NamesClass Consistency Principle(CCP)
Principle Type SOLID
TaglineA class should have only a single responsibility
Implementing
Design Patterns
Adapter Pattern
Command Pattern
Decorator Pattern
Facade Pattern
Factory Pattern
Mediator Pattern
Observer Pattern
Strategy Pattern
Related Principles Open/Closed Principle(OCP)
Liskov Substitution Principle(LSP)
Interface Segregation Principle(ISP)
Dependency Inversion Principle(DIP)

Definition

Single Responsibility Principle(SRP) is the first principle of the SOLID Principles.

Single Responsibility is a basic concept in software design, that says when we define a class- we should not give it more than one responsibility.

This Single Responsibility Principle(SRP) says-

A class should have only one responsibility (and a single reason for it to be changed).

Class should have only one job/responsibility, and that job/responsibility should be encapsulated inside that class.

A class should deal with only one subject/entity. SRP encourages us to create classes that are focused(towards only one subject), and selfish(only concerned about its own elements).

For example, if a class is dealing with employees, then it should only be responsible for employee information, and actions. It should not directly deal with employee salary, attendance, etc. We should define separate classes for salary, tax, etc., and separate class(es) for attendance. Then the Employee class can interact with those classes.

Also, we only make changes to the Employee class for only something related to the employee directly. For salary or attendance, we would change the relevant classes.

Single responsibility leads to smaller classes, which are combined and composed to create a complex system. This makes the code –

More readable.
Easily maintainable.
Easy to test.

This principle ensures maximum cohesion and minimum coupling. Cohesion indicates, how single-minded the class is.

WARNING

If a class has multiple responsibilities, then it should be refactored to multiple smaller classes(each of which has a single responsibility).

Implementation

Use the following steps to ensure the Single Responsibility Principle-

Break a large class(with lots of responsibility) into small classes, based on the entity and responsibility.
Compose and combine these classes(and/or objects of the classes) to create the full system.

Example 1#: Employee Information

Let’s consider an example that handles employee information, with salary, taxes and attendance tracking-

General Implementation (without SRP)

class Employee {
    private int id;
    private String name;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getInfo() {
        return "Employee id: " + id + ", name: " + name;
    }

    public void calculateSalary() {
        // Full logic to calculate the employee's salary
        System.out.println("Calculating salary");
    }

    public void calculateTax() {
        // Full logic to calculate the tax
        System.out.println("Calculating tax");
    }

    public void trackAttendance() {
        // Full logic to track employee attendance
        System.out.println("Tracking attendance");
    }

    public static void main(String[] args) {
        Employee employee = new Employee(1, "Bigbox Emp");
        System.out.println(employee.getInfo());
        employee.calculateSalary();
        employee.calculateTax();
        employee.trackAttendance();
    }
}
Java
class Employee:
    def __init__(self, id: int, name: str) -> None:
        self.id = id
        self.name = name

    def get_info(self) -> str:
        return f"Employee id: {self.id}, name: {self.name}"

    def calculate_salary(self) -> None:
        """Full logic to calculate the employee's salary"""

        print("Calculating salary")

    def calculate_tax(self) -> None:
        """Full logic to calculate the tax"""

        print("Calculating tax")

    def track_attendance(self) -> None:
        """Full logic to track employee attendance"""

        print("Tracking attendence")


# Demo usage
if __name__ == "__main__":
    employee = Employee(1, "Bigbox Emp")
    print(employee.get_info())
    employee.calculate_salary()
    employee.calculate_tax()
    employee.track_attendance()
Python
package main

import "fmt"

type Employee struct {
    ID   int
    Name string
}

func (e Employee) GetInfo() string {
    return fmt.Sprintf("Employee id: %d, name: %s", e.ID, e.Name)
}

func (e Employee) CalculateSalary() {
    // Full logic to calculate the employee's salary
    fmt.Println("Calculating salary")
}

func (e Employee) CalculateTax() {
    // Full logic to calculate the tax
    fmt.Println("Calculating tax")
}

func (e Employee) TrackAttendance() {
    // Full logic to track employee attendance
    fmt.Println("Tracking attendance")
}

func main() {
    employee := Employee{ID: 1, Name: "Bigbox Emp"}
    fmt.Println(employee.GetInfo())
    employee.CalculateSalary()
    employee.CalculateTax()
    employee.TrackAttendance()
}
Go
<?php

class Employee {
    private $id;
    private $name;

    public function __construct(int $id, string $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function getInfo(): string {
        return "Employee id: $this->id, name: $this->name";
    }

    public function calculateSalary(): void {
        // Full logic to calculate the employee's salary
        echo "Calculating salary\n";
    }

    public function calculateTax(): void {
        // Full logic to calculate the tax
        echo "Calculating tax\n";
    }

    public function trackAttendance(): void {
        // Full logic to track employee attendance
        echo "Tracking attendance\n";
    }
}

// Demo usage
$employee = new Employee(1, "Bigbox Emp");
echo $employee->getInfo() . "\n";
$employee->calculateSalary();
$employee->calculateTax();
$employee->trackAttendance();
PHP
class Employee {
    private id: number;
    private name: string;

    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }

    getInfo(): string {
        return `Employee id: ${this.id}, name: ${this.name}`;
    }

    calculateSalary(): void {
        console.log("Calculating salary");
    }

    calculateTax(): void {
        console.log("Calculating tax");
    }

    trackAttendance(): void {
        console.log("Tracking attendance");
    }
}

// Demo usage
const employee = new Employee(1, "Bigbox Emp");

console.log(employee.getInfo());
employee.calculateSalary();
employee.calculateTax();
employee.trackAttendance();
TypeScript

Implementation with SRP

class Employee {
    private int id;
    private String name;

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getInfo() {
        return "Employee id: " + id + ", name: " + name;
    }
}

class SalaryCalculator {
    public void calculateSalary(Employee employee) {
        System.out.println("Calculating salary for " + employee.getInfo());
    }
}

class TaxCalculator {
    public void calculateTax(Employee employee) {
        System.out.println("Calculating tax for " + employee.getInfo());
    }
}

class AttendanceTracker {
    public void trackAttendance(Employee employee) {
        System.out.println("Tracking attendance for " + employee.getInfo());
    }
}

public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee(1, "Bigbox Emp");

        SalaryCalculator salaryCalculator = new SalaryCalculator();
        TaxCalculator taxCalculator = new TaxCalculator();
        AttendanceTracker attendanceTracker = new AttendanceTracker();

        System.out.println(employee.getInfo());
        salaryCalculator.calculateSalary(employee);
        taxCalculator.calculateTax(employee);
        attendanceTracker.trackAttendance(employee);
    }
}
Java
class Employee:
    def __init__(self, id: int, name: str) -> None:
        self.id = id
        self.name = name

    def get_info(self) -> str:
        return f"Employee id: {self.id}, name: {self.name}"


class SalaryCalculator:
    def calculate_salary(self, employee: Employee) -> None:
        """Full logic to calculate the employee's salary"""

        print(f"Calculating salary for {employee.name}")


class TaxCalculator:
    def calculate_tax(self, employee: Employee) -> None:
        """Full logic to calculate the employee's tax"""

        print(f"Calculating tax for {employee.name}")


class AttendanceTracker:
    def track_attendance(self, employee: Employee) -> None:
        """Full logic to track employee attendance"""

        print(f"Tracking attendance for {employee.name}")


# Demo usage
if __name__ == "__main__":
    employee = Employee(1, "Bigbox Emp")

    # Using separate classes for different responsibilities
    salary_calculator = SalaryCalculator()
    tax_calculator = TaxCalculator()
    attendance_tracker = AttendanceTracker()

    print(employee.get_info())

    salary_calculator.calculate_salary(employee)
    tax_calculator.calculate_tax(employee)
    attendance_tracker.track_attendance(employee)
Python
package main

import "fmt"

type Employee struct {
    ID   int
    Name string
}

func (e Employee) GetInfo() string {
    return fmt.Sprintf("Employee id: %d, name: %s", e.ID, e.Name)
}

type SalaryCalculator struct{}

func (s SalaryCalculator) CalculateSalary(employee Employee) {
    fmt.Printf("Calculating salary for %s\n", employee.GetInfo())
}

type TaxCalculator struct{}

func (t TaxCalculator) CalculateTax(employee Employee) {
    fmt.Printf("Calculating tax for %s\n", employee.GetInfo())
}

type AttendanceTracker struct{}

func (a AttendanceTracker) TrackAttendance(employee Employee) {
    fmt.Printf("Tracking attendance for %s\n", employee.GetInfo())
}

func main() {
    employee := Employee{ID: 1, Name: "Bigbox Emp"}

    salaryCalculator := SalaryCalculator{}
    taxCalculator := TaxCalculator{}
    attendanceTracker := AttendanceTracker{}

    fmt.Println(employee.GetInfo())
    salaryCalculator.CalculateSalary(employee)
    taxCalculator.CalculateTax(employee)
    attendanceTracker.TrackAttendance(employee)
}
Go
<?php

class Employee {
    private $id;
    private $name;

    public function __construct(int $id, string $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function getInfo(): string {
        return "Employee id: $this->id, name: $this->name";
    }
}

class SalaryCalculator {
    public function calculateSalary(Employee $employee): void {
        echo "Calculating salary for " . $employee->getInfo() . "\n";
    }
}

class TaxCalculator {
    public function calculateTax(Employee $employee): void {
        echo "Calculating tax for " . $employee->getInfo() . "\n";
    }
}

class AttendanceTracker {
    public function trackAttendance(Employee $employee): void {
        echo "Tracking attendance for " . $employee->getInfo() . "\n";
    }
}

// Demo usage
$employee = new Employee(1, "Bigbox Emp");

$salaryCalculator = new SalaryCalculator();
$taxCalculator = new TaxCalculator();
$attendanceTracker = new AttendanceTracker();

echo $employee->getInfo() . "\n";
$salaryCalculator->calculateSalary($employee);
$taxCalculator->calculateTax($employee);
$attendanceTracker->trackAttendance($employee);
PHP
class Employee {
    private id: number;
    private name: string;

    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }

    getInfo(): string {
        return `Employee id: ${this.id}, name: ${this.name}`;
    }
}

class SalaryCalculator {
    calculateSalary(employee: Employee): void {
        console.log(`Calculating salary for ${employee.getInfo()}`);
    }
}

class TaxCalculator {
    calculateTax(employee: Employee): void {
        console.log(`Calculating tax for ${employee.getInfo()}`);
    }
}

class AttendanceTracker {
    trackAttendance(employee: Employee): void {
        console.log(`Tracking attendance for ${employee.getInfo()}`);
    }
}

// Demo usage
const employee = new Employee(1, "Bigbox Emp");

const salaryCalculator = new SalaryCalculator();
const taxCalculator = new TaxCalculator();
const attendanceTracker = new AttendanceTracker();

console.log(employee.getInfo());
salaryCalculator.calculateSalary(employee);
taxCalculator.calculateTax(employee);
attendanceTracker.trackAttendance(employee);
TypeScript

Example #2: Report Generation

In this example, we are considering report generation(in pdf format) and sending the report-

General Implementation (without SRP)

class Report {

    public void generateReport() {
        processReportData();
        generatePDF();
    }

    public void processReportData() {
        // Full logic for getting and processing data for the report
        System.out.println("Getting data for report, and processing data.");
    }

    public void generatePDF() {
        // Full logic for generating PDF from the processed data
        System.out.println("Generating PDF from data");
    }

    public void sendReport() {
        // Code for sending the report (via email)
        System.out.println("Sending report via email");
    }

    public static void main(String[] args) {
        Report report = new Report();
        report.generateReport();
        report.sendReport();
    }
}
Java
class Report:
    def generate_report(self):
        self.process_report_data()
        self.generate_pdf()
    
    def process_report_data(self):
        '''Full logic of getting and processing data for the report'''
        
        print("Getting data for report, and processing data.")
    
    def generate_pdf(self):
        '''Full processing code for generating PDF from the processed data'''
        print("Generating pdf from data")
    
    def send_report(self):
        '''Code for sending report(via email)'''
        
        print("Sending report via email")
        
        
# Demo useage
if __name__ == "__main__":
    report = Report()
    report.generate_report()
    report.send_report()
Python
package main

import "fmt"

type Report struct{}

func (r Report) GenerateReport() {
    r.ProcessReportData()
    r.GeneratePDF()
}

func (r Report) ProcessReportData() {
    fmt.Println("Getting data for report, and processing data.")
}

func (r Report) GeneratePDF() {
    fmt.Println("Generating PDF from data")
}

func (r Report) SendReport() {
    fmt.Println("Sending report via email")
}

func main() {
    report := Report{}
    report.GenerateReport()
    report.SendReport()
}
Go
<?php

class Report {

    public function generateReport() {
        $this->processReportData();
        $this->generatePDF();
    }

    public function processReportData() {
        // Full logic for getting and processing data for the report
        echo "Getting data for report, and processing data.\n";
    }

    public function generatePDF() {
        // Full logic for generating PDF from the processed data
        echo "Generating PDF from data\n";
    }

    public function sendReport() {
        // Code for sending the report (via email)
        echo "Sending report via email\n";
    }
}

// Demo usage
$report = new Report();
$report->generateReport();
$report->sendReport();
PHP
class Report {

    generateReport(): void {
        this.processReportData();
        this.generatePDF();
    }

    processReportData(): void {
        // Full logic for getting and processing data for the report
        console.log("Getting data for report, and processing data.");
    }

    generatePDF(): void {
        // Full logic for generating PDF from the processed data
        console.log("Generating PDF from data");
    }

    sendReport(): void {
        // Code for sending the report (via email)
        console.log("Sending report via email");
    }
}

// Demo usage
const report = new Report();
report.generateReport();
report.sendReport();
TypeScript

Implementation with SRP

class ReportDataProcessor {
    public String processReportData() {
        // Full logic for getting and processing data for the report
        System.out.println("Getting data for report, and processing data.");
        return "Processed Report Data";  // Return the processed data
    }
}

class PDFGenerator {
    public String generatePDF(String data) {
        // Full processing code for generating PDF from the processed data
        System.out.println("Generating PDF from data: " + data);
        return "Report.pdf";  // Return the generated PDF
    }
}

class ReportSender {
    public void sendReport(String pdf) {
        // Code for sending the report (via email)
        System.out.println("Sending report: " + pdf + " via email");
    }
}

public class Main {
    public static void main(String[] args) {
        ReportDataProcessor dataProcessor = new ReportDataProcessor();
        PDFGenerator pdfGenerator = new PDFGenerator();
        ReportSender reportSender = new ReportSender();

        // Process report data
        String processedData = dataProcessor.processReportData();

        // Generate PDF from the processed data
        String pdfFile = pdfGenerator.generatePDF(processedData);

        // Send the generated PDF report
        reportSender.sendReport(pdfFile);
    }
}
Java
class ReportDataProcessor:
    def process_report_data(self):
        """Full logic of getting and processing data for the report"""

        print("Getting data for report, and processing data.")
        return "Processed Report Data"  # Return the processed data


class PDFGenerator:
    def generate_pdf(self, data):
        """Full processing code for generating PDF from the processed data"""

        print(f"Generating PDF from data: {data}")
        return "Report.pdf"  # Return the generated PDF


class ReportSender:
    def send_report(self, pdf):
        """Code for sending report (via email)"""

        print(f"Sending report: {pdf} via email")


# Demo usage
if __name__ == "__main__":
    data_processor = ReportDataProcessor()
    pdf_generator = PDFGenerator()
    report_sender = ReportSender()
    
    # Process report data
    processed_data = data_processor.process_report_data()
    
    # Generate PDF from the processed data
    pdf_file = pdf_generator.generate_pdf(processed_data)
    
    # Send the generated PDF report
    report_sender.send_report(pdf_file)
Python
package main

import "fmt"

type ReportDataProcessor struct{}

func (r ReportDataProcessor) ProcessReportData() string {
    fmt.Println("Getting data for report, and processing data.")
    return "Processed Report Data"
}

type PDFGenerator struct{}

func (p PDFGenerator) GeneratePDF(data string) string {
    fmt.Printf("Generating PDF from data: %s\n", data)
    return "Report.pdf"
}

type ReportSender struct{}

func (r ReportSender) SendReport(pdf string) {
    fmt.Printf("Sending report: %s via email\n", pdf)
}

func main() {
    dataProcessor := ReportDataProcessor{}
    pdfGenerator := PDFGenerator{}
    reportSender := ReportSender{}

    // Process report data
    processedData := dataProcessor.ProcessReportData()

    // Generate PDF from the processed data
    pdfFile := pdfGenerator.GeneratePDF(processedData)

    // Send the generated PDF report
    reportSender.SendReport(pdfFile)
}
Go
<?php

class ReportDataProcessor {
    public function processReportData() {
        // Full logic for getting and processing data for the report
        echo "Getting data for report, and processing data.\n";
        return "Processed Report Data";  // Return the processed data
    }
}

class PDFGenerator {
    public function generatePDF(string $data) {
        // Full processing code for generating PDF from the processed data
        echo "Generating PDF from data: $data\n";
        return "Report.pdf";  // Return the generated PDF
    }
}

class ReportSender {
    public function sendReport(string $pdf) {
        // Code for sending the report (via email)
        echo "Sending report: $pdf via email\n";
    }
}

// Demo usage
$dataProcessor = new ReportDataProcessor();
$pdfGenerator = new PDFGenerator();
$reportSender = new ReportSender();

// Process report data
$processedData = $dataProcessor->processReportData();

// Generate PDF from the processed data
$pdfFile = $pdfGenerator->generatePDF($processedData);

// Send the generated PDF report
$reportSender->sendReport($pdfFile);
PHP
class ReportDataProcessor {
    processReportData(): string {
        // Full logic for getting and processing data for the report
        console.log("Getting data for report, and processing data.");
        return "Processed Report Data";  // Return the processed data
    }
}

class PDFGenerator {
    generatePDF(data: string): string {
        // Full processing code for generating PDF from the processed data
        console.log(`Generating PDF from data: ${data}`);
        return "Report.pdf";  // Return the generated PDF
    }
}

class ReportSender {
    sendReport(pdf: string): void {
        // Code for sending the report (via email)
        console.log(`Sending report: ${pdf} via email`);
    }
}

// Demo usage
const dataProcessor = new ReportDataProcessor();
const pdfGenerator = new PDFGenerator();
const reportSender = new ReportSender();

// Process report data
const processedData = dataProcessor.processReportData();

// Generate PDF from the processed data
const pdfFile = pdfGenerator.generatePDF(processedData);

// Send the generated PDF report
reportSender.sendReport(pdfFile);
TypeScript

Cognitive Clarifications

What is the meaning of responsibility in SRP?

Primarily “responsibility” in SRP means the job/task the class is doing or is supposed to do.

From a different angle, ” responsibility ” refers to the reason for a class to be changed(in any case).

For example, if an employee class is handling employee information, employee tax calculation, and attendance, then that class has more than one responsibility. We should use a separate class for employee info, another separate class for tax, and another class for attendance.

How do I refactor an existing codebase to implement SRP?

If there is an existing codebase where we want to implement SRP, in that case-

– first isolate obvious violations of SRP into separate classes, which have unrelated functionalities.
– then incrementally refactor the areas that you are not completely sure about.
– write tests to make sure that none of the functionalities are broken.

For example – if a class is responsible for processing data, and then writing data to some resource(file, database), then the class has more than one responsibility. We should separate the data processing and data writing into 2 separate classes.

How do we find the correct balance of SRP with project maintainability?

We should group the related responsibilities logically. Though SRP suggests separation, but related functionality can sometimes exist in one class if it makes sense in any specific context.

Does SRP make the code more complex?

No, it does not make the system complex.

As part of the SRP guideline, we are creating more classes, which might trick us into thinking that we are making the system complex.

But, in the long run, the separation of responsibility will make the code implementation more maintainable. As the responsibilities are clearly separated.

Are there any practical scenarios, when we can violate SRP?

In some cases, small violations are acceptable.

Like, when we have a simple small system that has no possibility of expanding later, and we don’t want to complicate it by creating lots of classes.

But, if there is a possibility of expansion in the future, then it is better to follow SRP.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.