Do you REALLY know what SOLID means? (#1: Single Responsibility Principle)

Single Responsibility Principle:

Do one thing and do it well!
This principle states that a class should only have one responsibility. Furthermore, it should only have one reason to change. SRP, like most principles out there, can be over-applied. If we create a new class for incrementing integers, then yeah, that may be a single responsibility, but that’s ridiculous! We tend to forget that things like the SOLID principles are there for a purpose. SOLID is a means to an end, not an end in itself. The end is maintainability.

Let’s consider a scenario. We can think of refactoring a class with two methods into two classes of one method each, so as to reduce the number of services injected into its constructor. We can think that this is a simplifying operation, in line with the SRP (single responsibility principle), when all it does is increases the number of classes to manage as well as adding an interface, in the process adding a lot of boilerplate code. More often than not; due to blindly and wrongly following SRP makes our code base more difficult to understand and debug, destroy encapsulation by the need to make everything public in order for the class fragments to be able to communicate; and using dependency injection sometimes made impractical by having to inject so many microscopic services in order to get any work done.

Intuitively, it seems to be wrong that our unit of organisation and encapsulation should be used to encapsulate a single method, or even one or two very short methods. In that case, why even bother with classes, and why not go back to procedural coding? In some specific cases it might be justified but we need to be sure of why we would do this.

Reason To Change

We need to pay close attention to the term ‘reason to change’. For example a class for an employee which has methods for calculating and reporting pay, or a class which has data manipulation and data persistence operations; these might be different ‘reason to change’. Also; a reason to change is related to a function in the business which is served by the software: in these cases the classes should be split up because the business function of creating reports and the business function of defining how salaries are calculated are different.

On the face of it, SRP is about where a class has functionality that belongs in two different layers or modules, it should be split. But when people who are following the SRP blindly without understanding the philosophy behind it, they are doing something more general and using it to cut up any class that does ‘more than one thing’.

Another apt and proper wording for the SRP is: Gather together the things that change for the same reasons. Separate those things that change for different reasons. It simply means The SRP is about limiting the impact of change.

solid principle
S (single responsibility principle) OLID
Implementing SRP

The trick of implementing SRP in our software is knowing the responsibility of each class. However, every developer has their vision of the class purpose, which makes things tricky. Sometimes only we, as designers of our application, can decide if something is in the scope of a class or not. When writing a class according to the SRP principle, we have to think about the problem domain, business needs, and application architecture. It is very subjective, which makes implementing this principle harder then it seems.

Let’s consider a class that contains code that changes the text in some way. 

public class TextManipulator {
    private String text;

    public TextManipulator(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void appendText(String newText) {
        text = text.concat(newText);
    }
    
    public String findWordAndReplace(String word, String replacementWord) {
        if (text.contains(word)) {
            text = text.replace(word, replacementWord);
        }
        return text;
    }
    
    public String findWordAndDelete(String word) {
        if (text.contains(word)) {
            text = text.replace(word, "");
        }
        return text;
    }

    public void printText() {
        System.out.println(textManipulator.getText());
    }
}

Here we have two responsibilities: manipulating and printing the text. Having a method that prints out text in this class violate the Single Responsibility Principle. For this purpose, we should create another class, which will only handle printing text.

Delegating Responsibility
public class TextPrinter {
    TextManipulator textManipulator;

    public TextPrinter(TextManipulator textManipulator) {
        this.textManipulator = textManipulator;
    }

    public void printText() {
        System.out.println(textManipulator.getText());
    }

    public void printOutEachWordOfText() {
        System.out.println(Arrays.toString(textManipulator.getText().split(" ")));
    }

    public void printRangeOfCharacters(int startingIndex, int endIndex) {
        System.out.println(textManipulator.getText().substring(startingIndex, endIndex));
    }
}

Now, in this class, we can create methods for as many variations of printing text as we want, because that’s its job. When designing software based on the SRP principle, cohesion is essential, since it helps us to find single responsibilities for our classes. This concept also helps us find classes that have more than one responsibility.
Let’s go back to our TextManipulator class methods:

...

public void appendText(String newText) {
    text = text.concat(newText);
}

public String findWordAndReplace(String word, String replacementWord) {
    if (text.contains(word)) {
        text = text.replace(word, replacementWord);
    }
    return text;
}

public String findWordAndDelete(String word) {
    if (text.contains(word)) {
        text = text.replace(word, "");
    }
    return text;
}

...

Here, we have a clear representation of what this class does: Text manipulation. But, if we don’t think about cohesion and we don’t have a clear definition of what this class’s responsibility is, we could say that writing and updating the text are two different and separate jobs. Lead by this thought, we can conclude than these should be two separate classes: WriteText and UpdateText. In reality, we’d get two classes that are tightly coupled and loosely cohesive, which should almost always be used together. These three methods may perform different operations, but they essentially serve one single purpose: Text manipulation.

Conclusion

The hard thing to remember about the Single Responsibility Principle is that it is based on likely patterns of change, not dependency or functional relationships within the code. Neither the internal structure nor the external function and requirements of the code are key. Rather the nature of the business environment in which the code is undergoing change.

The key is not to overthink!