34

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?

migrated from stackoverflow.com Jun 28 '12 at 18:22

This question came from our site for professional and enthusiast programmers.

21

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.

  • I wish that computer science was more like actual science. Subjectivity has no place in real science. While the SOLID principles are fine in their own right, they need to be iterated upon to minimize subjectivity and maximize objectivity. That hasn't happened yet, which makes me question their legitimacy in the real world. – DarkNeuron Oct 25 '17 at 15:51
13

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.

  • 14
    That's just repeating the definition really, but actually adhering to srp is still pretty subjective. – Andy Mar 12 '14 at 21:37
7

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.
  • 2
    Regarding your second point, see uxmyths.com/post/931925744/… – Cameron Martin Mar 19 '14 at 12:59
  • 7
    Disagree strongly about 7+/-2 - the single responsibility principle is about semantic cohesion, not about arbitrary numbers. – JacquesB Jun 30 '15 at 10:23
  • 1
    Rule of thumbs do not need independent scientific proof. Modern scientific method is centuries old, architecture and engineering is millenia old. The rule of thumb for public methods is "several" and no of parameters is "a few." In other news, even though some children's drawings show otherwise people's arms do not come out of their heads[citation needed]. – abuzittin gillifirca Aug 28 '17 at 6:36
  • @CameronMartin Depending on your setup, the interface for a class may or may not be readily available for you to read. Searching a UI is hardly the same as writing code -- if I have to consult the documentation every minute, I'm, at least, doubling the time it takes to do any real work. – Clearer Apr 12 '18 at 8:31
6

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.

  • That's a great article, written by the man himself. – MrDustpan Dec 13 '16 at 19:46
4

To answer this, take a step back and consider the intention of the single responsibility principle. Why is it a recommended design principle in the first place?

The purpose of the principle is to "compartmentalize" the code base, so code relating to a single "responsibility" is isolated in a single unit. This makes it easier to find and understand the code, and more importantly, it means that changes to the "responsibility" will only impact a single unit of code.

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.

So what it a "responsibility" then? It is something which could conceivably change independently of other changes. Say you have program which can save some settings to a XML configuration file, and can read them back in from the file. Is this a single responsibility, or is "load" and "save" two different responsibilities? Any kind of change to the file format or structure would require changing both the load and save logic. It is therefore a single responsibility which should be represented by a single class. Now consider an app which can export some data in CVS, Excel and XML format. In this case, it is easy to imagine that one format could change without impacting the other. If you decide to change the delimiter in the CVS format, it should not effect the Excel output. So in this case it should be represented by three separate classes (presumable sharing the same interface).

2

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.

1

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.

1

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.