Tobi the dog.

What is the Single Responsibility principle?

We’re going to take a look at the Single Responsibility Principle and how it can help you write better JavaScript.

Let’s pretend you’ve written a Dog class and have come up with a few things that most dogs do. Initially, you might write something like this.

class Dog {
  constructor() {
    this.isHungry = true;
  }

  init() {
    console.log("Woof!");
    this.isHungry = true;
    this.lastEaten = new Date();
    console.log("(yawn)");
    this.isSleepy = true;
    setTimeout(() => {
      this.isSleepy = false;
      console.log("(wags tail)");
    }, 3000);
    this.lastPooped = new Date();
  }
}

const dog = new Dog();

dog.init();

If you ran this code, you’d see that the dog barks (at least in the console), eats, sleeps and poops*.

At least I think that’s what’s happening. It’s a bit hard to tell because all the functionality and logic has been crammed into the init method.

It would be nice if this Dog class was easier to read and understand.

*I hope you’re not offended that my dog poops. That’s what they do.

Single responsibility to the rescue

A module should be responsible to one, and only one, actor.

Robert C. Martin

There’s a million different explanations of what the Single Responsibility Principle (SRP) means and how it works. Here’s my summary: Something should never do some things, but only do one thing.

Most of the software development examples I’ve seen online show how to apply this principle to classes, but it can also be applied to class methods and functions as well.

Moving each responsibility to its own method

Let’s refactor the Dog class with SRP in mind. We shouldn’t have the dog doing everything in the init method, so let’s move this functionality to separate methods.

class Dog {
  /* ... */

  bark() {
    console.log("Woof!");
  }

  eat() {
    this.lastEaten = new Date();
    this.isHungry = true; // Not a mistake. Dogs are always hungry.
  }

  sleep() {
    console.log("(yawn)");
    this.isSleepy = true;
    setTimeout(() => {
      this.isSleepy = false;
      console.log("(wags tail)");
    }, 3000);
  }

  poop() {
    this.lastPooped = new Date();
  }

  /* ... */
}

Now it’s clearer what is going on in the Dog class, as each method has a single responsibility. And instead of executing a bunch of logic in the init method, we can call each of these methods within the init method:

Class Dog
  /* ... */

  init() {
    // Bark
    this.bark();

    // Eat
    this.eat();

    // Sleep
    this.sleep();

    // Poop
    this.poop();
  }

  /* ... */
}

In fact, it’s so clear now, you don’t even need those comments.

Class Dog
  /* ... */

  init() {
    this.bark();
    this.eat();
    this.sleep();
    this.poop();
  }

  /* ... */
}

Now that it’s easier to read, we can more easily fix bugs. Did you notice how the bark method actually sends “Woof!” to the console? Let’s fix that inconsistency.

class Dog {
  /* ... */

  bark() {
    console.log("Bark!");
  }

  /* ... */
}

Resources: