Single Responsibility Principle with JavaScript
SOLID - An ideal approach to write clean code!
Hey there, welcome back to my blog.
In this article and near-future ones, we will explore the SOLID principles together with JavaScript code examples.
What are SOLID principles?
SOLID stands for
S — Single responsibility principle
O — Open closed principle
L — Liskov substitution principle
I — Interface segregation principle
D — Dependency Inversion principle
By following these 5 well-known principles, you will write better-quality easy to reason about, and predictable code.
Let's get started with the first principle in this article.
S - Single Responsibility Principle
It's probably my favorite one and the easiest principle to understand at the same time.
⚠️ One caveat: This one is also the most misunderstood one.
A class (or module, function) should be responsible for only one actor, in other words, it should have one Single Responsibility, doing one thing. As a consequence, it has only one reason to change.
To better understand this point, let's take a look
Bad:
class TodoList {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
removeItem(id) {
this.items = this.items.filter((item) => item.id !== id);
}
save(filename) {
fs.writeFileSync(filename, this.toString());
}
login(username, password) {
// ...
}
}
Even though this class seems to be fine...
We're violating the Single responsibility principle. We added 2 more responsibilities to the TodoList
class, which are authentication
and database
related stuff.
We should minimize the amount of times we need to change a class.
It's very important because if we delegate too much functionality to one class
The class itself has many reasons to change
When we modify a piece of the class, it can be difficult to understand or predict how those changes will affect other dependent modules or another use case in the codebase.
Good:
class TodoList {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
removeItem(id) {
this.items = this.items.filter((item) => item.id !== id);
}
}
class UserAuthentication {
login(username, password) {
// ...
}
}
class DataBaseManagement {
save(filename) {
fs.writeFileSync(filename, this.toString());
}
}
Our code has become much more scalable and maintainable.
We don't want to give the TodoList
class too much responsibility that does not relate to its main purpose.
Imagine any time you want to modify the logic of save
function - that is 100% related to DataBaseManagement
or add more logic to login
function, you have go to the TodoList
to make these changes.
It does not make sense.
Obviously, it might not seem very useful now when we're working with a small example. But please keep this principle in mind every time you write code for a big application with complex architecture, this principle really nails.
On top of that, small classes, modules, or functions are usually easier to work with, and easier to read, maintain, test, errors caused by these are also predictable as well.
Conclusion
I hope you enjoy the article!
Please like and consider to subscribe if you want to see more content like this one.
I'm looking forward to seeing you in the next one soon!