Single Responsibility Principle with JavaScript

SOLID - An ideal approach to write clean code!

·

3 min read

Single Responsibility Principle with JavaScript

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

  1. The class itself has many reasons to change

  2. 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!