The single responsibility is very simple:
A class should have only one reason to change.
Robert C. Martin, SRP - The single responsibility principle
As an example let’s look at a sensor class, which should report it’s status and readings into a text file:
The class sensor has multiple responsibilities: it represents the sensor with it’s data, knows how an export file content should look like and how to export it to the filesystem.
This is clearly a violation of the single responsiblity principle.
Why is this a problem?
There are different reasons to change the class. Maybe a change of the report file format, the sensor data or the filesystem logic.
Because everything is coupled thightly together in a single class it’s not possible to extend or test the class.
Let’s look at a refactored version, which has 3 classes:
Sensor with it’s data is the primary object. The report generator implements an interface, which allows the add different report generators later. The filesystem access is encapsuled in another class.
This refactoring has the side-effect that we can unit-test the report generator:
That’s it?
The current solution generates the text file format and knows that it’s stored somehow with an implementation of the IFilesystem Interface.
But does the report generator only one thing?
Not really, it generates the content of the file and delegates the file system operation So in fact our current solution does more then 2 things. If we want to add another file format, maybe XML, we have to extract the delegation to save the report file.
Let’s refactor again!
Okay, we’ve noticed that the report generator does more then one thing. We’re also only able to test it with a mock instance of IFilesystem. So there must be another way to solve this problem.
I refactored the next solution and pushed the filesystem logic one layer. This way we encapsulate what varies – the content of the reports. The file handling itself is the same for all formats.
This version has even more, but smaller classes. This is required a bit of wiring at the startup of the application.
Conclusion
As with all other principles, you should know and understand them to know when and why to use them. Unneeded flexibility is unnecessary complexity – which is a bad thing.
As you’ve seen, this principle is very helpful to reduce the size of your classes and to make the code more testable.
To make the wiring of the instances less painful you may use the factory pattern and / or IoC Containers.
Full project: https://github.com/zs40x/Single_Responsibility_Principle