The Single Responsibility Principle is based on the high cohesion principle. The difference between the two is that a highly cohesive classes features a set of responsibilities that are strongly related, while classes adhering to SRP have just one responsibility.
But how do we determine whether a particular class features a set of responsibilities and is thus just highly cohesive, or whether it has only one responsibility and thus adheres to SRP? In other words, isn’t it more or less subjective, since some may consider a class very granular (and as such will believe the class adheres to SRP), while others may consider it not granular enough?
Why yes it is very subjective, and it is the subject of many heated, red-faced debates programmers get into.
There’s not really any one answer, and the answer may change as your software becomes more complex. What was once a single well-defined task may eventually become multiple poorly-defined tasks. That’s always the rub too. How do you choose the proper way to divide a program up into tasks?
About the only advice I can give is this: use your (and your coworkers’) best judgement. And remember that mistakes can (usually) be corrected if you catch them soon enough.
Bob Martin (Uncle Bob), who originated the SOLID principles of which SRP is the first, says about this (I am paraphrasing, can’t recall the actual words):
A class should only have one reason to change
If it has more than one reason, it does not adhere to SRP.
I can give you several rules of thumb.
- How easy is it to name the class? If a class is difficult to name, it is probably doing too much.
- How many public methods does the class have? 7+/-2 is a good rule of thumb. If the class has more than that, you should think about splitting it into several classes.
- Are there cohesive groups of public methods used in separate contexts?
- How many private methods or data members are there? If the class has a complex internal structure, you probably should refactor it so that the internals are packaged into separate smaller classes.
- And the easiest rule of thumb: how big is the class? If you have a C++ header file containing a single class that is more than a couple of hundred lines long, you should probably split it up.
Single Responsibility Principles says that each software module should have only one reason to change. On a recent article Uncle Bob explained “reason to change”,
However, as you think about this principle, remember that the reasons for change are people. It is people who request changes. And you don’t want to confuse those people, or yourself, by mixing together the code that many different people care about for different reasons.
He further explained the concept with an example HERE.
Responsibilities are at the requirements level, not at the implementation level.
A program is designed to satisfy a set of requirements as defined by the stakeholders. Some of these requirements are most likely going to change over time for various reasons. Often one requirement can change without any other requirements changing. But we don’t know beforehand which requirements will change when!
The Single Responsibility Principle states that a module or class should only be responsible for satisfying one single requirement.
The purpose of this is to isolate code that should change behavior from code that should not change behavior, when a change is implemented. If the responsibilities for two independent requirements are tangled together in the same module, it is difficult to change one without affecting the other.
What you absolutely never want in a system is when when one small chance causes some other apparently unrelated part of the code to fail or change behavior. The SRP helps to isolate bugs and changes.
Even in the case where multiple requirements change at the same time, it is nice to be able to implement and test each change individually. This reduces risk.
Importantly, the principle does not extend to the implementation level. Often it is misunderstood to mean that a single module or class should only be responsible for doing “one thing”. But this just leads to fruitless discussions about what “one thing” really means. In the end, every operation consist of multiple operations down to the language primitives. At the implementation level, code should follow the low coupling and high cohesion principle.
An example. A shopping site might have defined a number of requirements:
- Before confirming, the use should be able to change the number of items in the basket.
- Sales tax at 9% should be added on the checkout page
- The background should be blue with the orange logo top left.
It is likely that each of these requirements could change in the future. Therefore there should not be a single class which is responsible for implementing all this. If the is a single
RenderShoppingBasket class which is responsible for both adding the sales tax and rending the logo, then the principle is broken.
OO says that classes are a grouping of data a functionality. This definition leaves plenty of room for subjective interpretation.
We do know that classes should be clearly and easily defined. But, in order to define such a class, we have to have a clear notion of how a class fits into the overall design. Without waterfall type requirements which, paradoxically, are considered an anti-pattern…this is difficult to achieve.
We can implement a class design with an architecture that works in most cases, like MVC. In MVC applications we only assume to have data, a user interface and a requirement for the two to communicate.
With a basic architecture, it’s easier to identify cases where single responsibility rules are being broken. E.G. Passing an instance of a User Control to a Modal.
Just for the sake of discussion, I will bring up a class from JUCE called AudioSampleBuffer. Now this class exists to hold a snippet (or perhaps a rather long snippet) of audio. It knows the number of channels, the number of samples (per channel), seems to be committed to 32-bit IEEE float rather than having a variable numeric representation or wordsize (but that is not a problem with me). There are member functions that allow you to get the numChannels or numSamples and pointers to any particular channel. You can make an AudioSampleBuffer longer or shorter. I presume the former zero-pads the buffer while the latter truncates.
There are a few private members of this class that are used for allocating space in the special heap that JUCE uses.
But this is what AudioSampleBuffer is missing (and I have had several discussions with Jules about it): a member called
SampleRate. How could it be missing that?
The single responsibility that an AudioSampleBuffer needs to fulfill is to adequately represent the physical audio that one hears that its samples represent. When you input an AudioSampleBuffer from something that reads a soundfile or from a stream, there is an additional parameter that you must get and pass it along with the AudioSampleBuffer to processing methods (say it’s a filter) that needs to know the sample rate or, eventually, to a method that plays the buffer out to be heard (or streams it to someplace else). Whatever.
But what you have to do is continue to pass this SampleRate, which is inherent to the specific audio living in the AudioSampleBuffer, around to everywhere. I have seen code where a constant 44100.0f was passed to a function, because the programmer didn’t seem to know what else to do.
This is an example of failing to meet its single responsibility.
A concrete way can be done, based on what you said – that high cohesion leads single responsibility you can measure cohesion. A maximal cohesive class has all the fields used in all the methods. While a maximal cohesive class is not always possible nor desirable to have it’s still best to reach to this. Having this class design goal it’s pretty easy to deduce that your class cannot have many methods or fields (some say at most 7).
Another way is from the pure basics of OOP – model after real objects. Its much more easier to see the responsibility of the real objects. However, if the real object is too complex break it into multiple containg objects each of has its own responsibility.
There is so much confusion over this.
Bob Martin (Uncle Bob) from his clean code blog: The Single Responsibility Principle (https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html) states the following:
“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.”
What does the SRP NOT mean?
- It does not mean to make every class as small as possible so that it only does one thing because it is small.
- It does not mean that there should only ever be the need for a single change to a class when it is being maintained.
- It does not mean that a class or function or module must be of any size at all.
what DOES it mean?
He’s purposefully vague when he talks about ‘things’ because this has different meaning in different environments but most of us can relate it to a package or namespace for practical intent although for some it may be a class or even file. It pertains to the fundamental ‘modularizing’ element of your environment whatever that is.
In use case terminology we often think of people having roles. This is clearly what he means in his example in the book “Clean Architecture” where he defines reasons for change in terms of the business roles of individuals using information from the system.
Now think of the use case itself. There may be several use cases that pertain just to a specific role or (actor). They may not have to change for any other purpose than to satisfy the requirements of the individual fulfilling this role. You can think of the use case as a duty of a person in a role or the “reason” for writing the code. There are also business “reasons” why the person in this role would want to change the code.
You may be implementing a single use case or even a few closely related use cases for a single role. If this is your case then these “highly cohesive” classes and functions should all be contained in the same module to satsify the SRP.
There may be several classes and possibly many functions per class and that is perfectly fine for all code necessary to satisfy the use case because the number of classes and functions involved are not part of the principle.
So code that is required to perform a duty for a user in a specific role belongs together in a module. Code for other duties for other user roles belong in different modules. Other duties may belong in a different module if they are not closely related even if they are for the same role.
We are gathering together the code that changes together for individuals in the same role and separating code that changes for different roles because their use cases or needs for even the same information are different or potentially divergent over time.
Modules are right-sized when they meet the principle. Not when they have x number of lines or consist of only a single class with a single function in them. These are NOT applications of the principle in any way, shape or form. Any correlation between the size of the classes or number of functions or methods in classes and the SRP is purely coincidental.
After reading many other people’s interpretation of the SRP I think I understand now why there is so much confusion. People are applying the ‘principle’ at multiple levels. I agree that a function (the smallest unit of lines of code related by name in your language) should be as specific as possible to the meaning of that name and that it should represent a single simple task.
But when applied to classes or modules the SRP has a completely different meaning as I have described in this post. It is by far the more useful application of the principle as it will keep you out of architectural trouble which is much harder to overcome than a simple case of breaking a function down into succinct operations which should always be done for the sake of readability but hardly worth declaring a ‘principle’ for.
Note also that it is unhelpful to simply apply this one principle and ignore all the others. There are architectural reasons for separating concerns (database access, models, framework, I/O and other classes). These mostly have to do with mitigating risks. That should be the focus of deciding which modules to create.
If the modules you create contain code that is:
- highly cohesive (highly interrelated) and
- constitute an efficient solution to the problem and
- does not co-mingle code specifically designed to satisfy divergent high-level business objectives and
- you have sufficiently encapsulated complexity away from other concerns and
- the functions in it have been factored enough to make them easy to understand
- module separation is true to the design (i.e. onion architecture or whatever pattern(s) etc… you are implementing)
then you should be satisfied with your solution with respect to the SRP.