SOLID Principles Made Ridiculously Simple: Single Responsibility Principle

Every self-declared programmer would have heard of the SOLID principles: A set of guiding principles for Object-oriented Programming. Unfortunately, most of the time, the way these principles are explained are hardly easy (read: confusing) for beginners to understand.

This is also the motivation for starting this blog: to explain code and ideas so easy that even a platypus can understand!

Without further ado, let’s take a look at the first principle, the Single Responsibility Principle (SRP).

Single Responsibility Principle

In the words of those who want to confuse us:

“The Single Responsibility Principle states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class, module or function.”

In simple platypus language:

“Be lazy, just do what you need to do!”

Well, let’s take a look at an example to see how this principle looks like in code:

class Car {
    public Model model;
    public Color color;
    public Car(Model model, Color color) {
        this.model = model;
        this.color = color;
    }
    public void drive() {
        System.out.println("Vroom vroom");
    }
    public void paintCar(Color newColor) {
        this.color = newColor; 
    }
}

In the code above, we see the implementations for the Car class. In particular, the car has methods drive and paintCar.

But wait…

Is a car supposed to be responsible for painting itself?

Remember, “Be lazy, just do what you need to do”! Does a car need to know how to paint itself? Absolutely not! In this case, our code is, therefore, violating the Single Responsibility Principle!

Now, let’s take a look at the new code after taking into account the Single Responsibility Principle:

interface CarPainter {   
    public void paintCar(Car car, Color newColor) 
} 

class CarWorkshop implements CarPainter {
    public void paintCar(Car car, Color newColor) {
        car.color = newColor;
    } 
}

class Car {
    public Model model;
    public Color color;   
    public Car(Model model, Color color) {
        this.model = model;     
        this.color = color;
    }   
    public void drive() {
        System.out.println("Vroom vroom");
    } 
}

Ah, much better! Now, the car is only responsible for driving. The car owner can now paint the car at the car workshop. In fact, he can go to any CarPainter and have the car painted, without the car knowing what’s going on!

For those who appreciate a diagram, here’s a class diagram to satisfy you.

One reason to change

A well-known litmus test to see if your module or class adheres to Single Responsibility Principle is if it has only one reason to change. What do we mean by that? Let’s refer to our old code to see how it works.

Say, for instance, cars are now required by law to drive with three vrooms instead of the usual two. In this case, we have to refactor the drive method. We say that the change in the driving process is a reason for the Car class to change.

Suppose that in addition, cars must be washed before they can be painted. Therefore, we have to refactor the paintCar method as well. Consequently, a change in the painting process is also a reason for the Car class to change.

From the above example, we can see that there are already two reasons for the old Car class to change, which is consistent with our conclusion that our old code is violating the Single Responsibility Principle.

See, intuitively, we know that a car should only be responsible for driving. It should not be responsible to paint itself! Therefore, the code in the Car class should really only contain code relevant to its purpose, which is driving.

The only time a car should need to paint!

The purpose of making our module/class to only contain code that is related to its purpose is so that when there are changes made in the future, only minimal components will be affected.

How to follow the Single Responsibility Principle

I always tell my platypus to follow these steps before writing any code:

  1. Define the purpose of the object. Is it supposed to drive? Is it supposed to store data? Is it suppose to parse strings?
  2. Draft the attributes that the object will have. Does the object need to know about these attributes?
  3. Draft the methods/functions that the object will have. Do these methods directly contribute to its purpose?

It’s also good to relook at old code to make sure that they are following the Single Responsibility Principle, because by following the SOLID principles, your code can remain easy to maintain even as your codebase scales up.

Have fun!

Comments are closed.