Summary
Principle Name | Single Responsibility Principle |
Acronym | SRP |
Other Names | Class Consistency Principle(CCP) |
Principle Type | SOLID |
Tagline | A 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 –
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-
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();
}
}
Javaclass 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()
Pythonpackage 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();
PHPclass 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();
TypeScriptImplementation 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);
}
}
Javaclass 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)
Pythonpackage 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);
PHPclass 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);
TypeScriptExample #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();
}
}
Javaclass 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()
Pythonpackage 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();
PHPclass 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();
TypeScriptImplementation 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);
}
}
Javaclass 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)
Pythonpackage 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);
PHPclass 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);
TypeScriptCognitive Clarifications
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.
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.
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.
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.
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.