Uncle Bob states in his book that this might be one of the least understood principles. I've seen lots of evidence of that in my career.
From his clean code blog:
The Single Responsibility Principle
Another wording for the Single Responsibility Principle is:
Gather together the things that change for the same reasons. Separate those things that change for different reasons.
If you think about this you’ll realize that this is just another way to define cohesion and coupling. We want to increase the cohesion between things that change for the same reasons, and we want to decrease the coupling between those things that change for different reasons.
However, as you think about this principle, remember that the reasons for change are people. It is people who request changes.
he also defines it as:
A module should be responsible to one, and only one, actor.
Yet another way to say this is that an SRP compatible component is one that when it does change it always changes with respect to its ability to fulfill a role for a specific audience. As a unit these functions satisfy a single need. Depending upon the language you use 'component' could mean a single package, class or file.
for example: consider a class that implements stove (from the post above) would have inputs (electricity/natural gas/propane) and outputs (BTUs and burner configurations and other features like self cleaning).
All of this stuff is related to the single job of cooking food and the cook is the single party that cares about combining these functions to achieve their goals. The SRP indicates that it is preferable to put all of these features in a single component. Some may be exposed and others completely internal.
The point is that there is very little reason to split these aspects away from the stove as they relate to the stove and to nothing else. Any cook can control these aspects through the stoves interface, the knobs and burners. Every cook wanting to use the stove will have exactly the same concerns and benefit from the interface in the same way. Because these functions are all inseparable from the stove (it isn't as useful without them and they don't apply to anything else) it is wise to collate them within it because they have high cohesion.
A frying pan on the other hand is a much smaller component that can be used with the stove but is not an integral part of it. When you replace the stove you don't necessarily have to replace the frying pan.
The pan may have a simple interface like:
fry ( foodItem ); stop();
FoodItems might only have:
So some components can be large and others small and where the line is drawn depends upon the component's usage which is why I think there is so much confusion about the principle.
In an oversimplified but more pertinent coding-related example the one 'person' may be a developer that wants to access files or something like that. Here the single purpose is file I/O and the 'audience' is developers. They probably expect a useful file class to have several related functions on it like:
a = file.read()
These functions have high cohesion because they are intimately related to each other with respect to the file object the class represents. They make the file object highly useful and internal aspects of the file that the outside world need not be concerned with are encapsulated.
one could separate them into separate classes and pass a file object (or worse, the file name) into them. i.e. something like:
a = fileReader(file);
pos = fileSeeker(file, c);
fileWriter(file, b, pos);
These objects "do one thing" but because they all operate on a file object and they are independent of other purposes splitting them up clutters the namespace for no gain. Note also that by turning the object 'inside out' the file position (pos) must now become an additional concern for the outside environment.
This supposed 'application' of the principle would really in fact be a 'violation' of it. There are many well-meaning but incorrect examples out there that would lead developers to the wrong conclusion. I think this is why Uncle Bob says it is one of the poorest understood of the SOLID principles.
It isn't very cohesive to break up a component that performs interrelated operations on the same object(s). It should only be done if there are multiple classes of 'consumers' that depend upon the the same feature set but to achieve different ends.