The Single Responsibility Principle states that a class should have one and only one reason to change. It also states that each class should only have one job and do it well.
A class should have one and only one reason to change.
The reason behind this principle is to separate responsibilities or behaviors so that debugging and refactoring shouldn't cause problems to other parts of a project.
A good way to see if a class adheres to this principle is to say out loud the responsibilities the class has and be on the lookout for and's. Every and should go on a separate class.
Example
Let's say we have an Employee Management
class that's responsible for basic CRUD operations over Employees and Logging the results on the console (👀). In this class, we have a function that pushes employees to an array and a function to log the result of the operation. The code would be something like this:
//EmployeeManagement.js
class EmployeeManagement {
constructor() {
this.employees = [];
}
addEmployee(employee) {
this.employees.push(employee);
this.logResult('[LOG] EMPLOYEE ADDED');
}
logResult(message) {
console.log(message);
}
}
Suppose that you're asked to apply the following changes to the code:
- Add a validation so that the employee's name isn't empty.
- Log operations results into a file and not the console.
Let's apply change number 1. The resulting code will be the following:
//EmployeeManagement.js
class EmployeeManagement {
constructor() {
this.employees = [];
}
addEmployee(employee) {
if (employee.name) {
this.employees.push(employee);
this.logResult('[LOG] EMPLOYEE ADDED');
} else {
this.logResult('[ERROR] NAME MUST NOT BE EMPTY');
}
}
logResult(message) {
console.log(message);
}
}
Validating that the employee's name is not empty is still a behavior expected from an Employee Management
class. The reason to change was to improve the creation of employees.
Now let's apply change 2. The resulting code will be the following:
//EmployeeManagement.js
class EmployeeManagement {
constructor() {
this.employees = [];
}
addEmployee(employee) {
if (employee.name) {
this.employees.push(employee);
this.logResult('[LOG] EMPLOYEE ADDED');
} else {
this.logResult('[ERROR] NAME MUST NOT BE EMPTY');
}
}
logResult(message) {
/*writeToFile is a mock function, just for educational purposes*/
writeToFile(message);
}
}
Logging the result of the operation into a file is not a behavior related to Employees Management
. Does it relate to CRUD operations?.
You can tell by now that the Employee Management
class had two reasons to change, going against the Single Responsibility Principle. Did you notice when I explained what the class was going to do I used one "and" 👀?
Let's say we have an
Employee Management
class that's responsible for basic CRUD operations over Employees and Logging the results on the console.
To apply the principle, we should have the logResult
function on a separate Logger
class so that if we ever want to change the way we log the results, it should be on its own, separate class. The refactor of the classes should be as follows:
//EmployeeManagement.js
class EmployeeManagement {
constructor() {
this.employees = [];
this.logger = new Logger();
}
addEmployee(employee) {
if (employee.name) {
this.employees.push(employee);
this.logger.logResult('[LOG] EMPLOYEE ADDED');
} else {
this.logger.logResult('[ERROR] NAME MUST NOT BE EMPTY');
}
}
}
//Logger.js
class Logger {
logResult(message) {
/*writeToFile is a mock function, just for educational purposes*/
writeToFile(message);
}
}
Remember that we want each class to have one and only one reason to change. With the new code, if we want to change the way we add an employee or the way we log the results of an operation we can do so in their own, separate classes, following the Single Responsibility Principle.
Top comments (0)