77

The Single Responsibility Principle states that a class should do one and only one thing. Some cases are pretty clear cut. Others, though, are difficult because what looks like "one thing" when viewed at a given level of abstraction may be multiple things when viewed at a lower level. I also fear that if the Single Responsibility Principle is honored at the lower levels, excessively decoupled, verbose ravioli code, where more lines are spent creating tiny classes for everything and plumbing information around than actually solving the problem at hand, can result.

How would you describe what "one thing" means? What are some concrete signs that a class really does more than "one thing"?

3
  • 7
    +1 for over the top "ravioli code". Early on in my career I was one of those people who took it too far. Not just with classes, but with method modularization too. My code was peppered with tons of little methods that did something simple, just for the sake of breaking up a problem into small chunks that could fit on the screen without scrolling. Obviously, this was often going way too far. Dec 29, 2010 at 20:44
  • @BobbyTables Ask your boss for a bigger monitor :-)
    – gnasher729
    Dec 17, 2019 at 17:08
  • That's not what the SRP says, see checked answer
    – user949300
    Dec 27, 2019 at 16:31

14 Answers 14

62

I really like the way Robert C. Martin (Uncle Bob) restates the Single Responsibility Principle (linked to PDF):

There should never be more than one reason for a class to change

It's subtly different from the traditional "should do only one thing" definition, and I like this because it forces you to change the way you think about your class. Instead of thinking about "is this doing one thing?", you instead think about what can change and how those changes affect your class. So for example, if the database changes does your class need to change? What about if the output device changes (for example a screen, or a mobile device, or a printer)? If your class needs to change because of changes from many other directions, then that's a sign that your class has too many responsibilities.

In the linked article, Uncle Bob concludes:

The SRP is one of the simplest of the principles, and one of the hardest to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities from one another is much of what software design is really about.

4
  • 2
    I like the way he states it too, it seems easier to check when related to change rather than the abstract "responsibility". Nov 5, 2010 at 18:33
  • That's actually an excellent way to put it. I love that. As a note, I usually tend to think of the SRP as applying much more strongly to methods. Sometimes a class just has to do two things (maybe its the class the bridges two domains), but a method should almost never be doing more than what you can succinctly describe by its type signature. Nov 5, 2010 at 19:56
  • 1
    Just showed this to my Grad - great read and a damn good reminder to myself. Nov 8, 2010 at 13:39
  • 2
    Ok, this makes a ton of sense when you combine it with the idea that non-overengineered code should only plan for change that's likely in the foreseeable future, not for every possible change. I'd restate this, then, slightly as "there should only be one reason for a class to change that is likely to occur in the foreseeable future". This encourages favoring simplicity in parts of the design that are unlikely to change and decoupling in parts that are likely to change.
    – dsimcha
    Jan 1, 2011 at 0:45
22

I keep asking myself what problem is SRP trying to solve? When does SRP help me? Here’s what I came up with:

You should refactor responsibility / functionality out of a class when:

1) You’ve duplicated functionality (DRY)

2) You find that your code needs another level of abstraction in order to help you understand it (KISS)

3) You find that pieces of functionality are understood by your domain experts as being apart of a different component (Ubiquitous Language)

You SHOULDN’T refactor responsibility out of a class when:

1) There isn’t any duplicated functionality.

2) The functionality doesn’t make sense outside of the context of your class. Put another way, your class provides a context in which it is easier to understand the functionality.

3) Your domain experts don’t have a concept of that responsibility.

It occurs to me that if SRP is applied to broadly, we trade one kind of complexity (trying to make heads or tails of a class with way too much going on inside) with another kind (trying to keep all of the collaborators / levels of abstraction straight in order to figure out what these classes actually do).

When in doubt, leave it out! You can always refactor later, when there is a clear case for it.

What do you think?

2
  • While these guidelines may or may not be helpful, it doesn't really have anything to do with the SRP as defined in SOLID, does it?
    – sara
    Jul 3, 2016 at 7:14
  • 2
    THANK you. I can't believe some of the insanity involved in so-called SOLID principles, where they make very simple code a hundred times more complex for no good reason. The points you give describe real world reasons to implement SRP. I think principles you gave above should become their own acronym, and throw "SOLID" out, it does more harm than good. "Architecture Astronauts" indeed, as you pointed out in the thread that lead me here. Jan 17, 2018 at 19:02
4

I don't know if there is an objective scale to it but what would give this away would be the methods - not so much the number of them but the variety of their function. I agree you can take decomposition too far but I wouldn't follow any hard and fast rules about it.

3

The Answer lies in the definition

What you define the responsibility to be, ultimately gives you the boundary.

Example:

I have a component that has the responsibility of displaying invoices -> in this case, if I started to add anything more then I'm breaking the principle.

If on the other hand if I said responsibility of handling invoices -> adding multiple smaller functions (eg printing Invoice, updating Invoice) all are within that boundary.

However if that module started to handle any functionality outside of invoices, then it would be outside of that boundary.

3
  • I guess the main problem here is the word "handling". It's too generic to define a single responsibility. I think it would be better to have a component responsible for print invoice and another one for update invoice rather than a single component handle{print, update, and - why not ? - display, whatever} invoice.
    – Machado
    Dec 30, 2010 at 3:27
  • 1
    The OP is essentially asking "How do you define a responsibility?" so when you say the responsibility is whatever you define it to be seems like it's just repeating the question.
    – Despertar
    Jul 17, 2013 at 22:31
  • "My class is responsible for life, the universe, and all the rest." :)
    – Lagerbaer
    Feb 21, 2020 at 21:36
2

I always view it on two levels:

  • I make sure my methods only do one thing and do it well
  • I see a class as a logical (OO) grouping of those methods that represents one thing well

So something like a domain object called Dog:

Dog is my class but Dogs can do many things! I might have methods such as walk(), run() and bite(DotNetDeveloper spawnOfBill) (sorry couldn't resist ;p).

If Dog becomes to unwieldy then I'd think about how groups of those methods can be modeled together in another class, such as a Movement class, which could contain my walk() and run() methods.

There's no hard and fast rule, your OO design will evolve over time. I try to go for a clear cut interface/public API as well as simple methods that do one thing and one thing well.

2
  • Bite should really take an instance of Object, and a DotNetDeveloper should be a subclass of Person (usually, anyway!) Nov 5, 2010 at 15:00
  • @Alan - There - fixed that for you :-) Nov 5, 2010 at 15:08
1

I look at it more along the lines of a class should only represent one thing. To appropriate @Karianna's example, I have my Dog class, which has methods for walk(), run() and bark(). I'm not going to add methods for meaow(), squeak(), slither() or fly() because those aren't things that dogs do. They are things that other animals do, and those other animals would have their own classes to represent them.

(BTW, if your dog does fly, then you should probably stop throwing him out of the window).

5
  • +1 for "if your dog does fly, then you should probably stop throwing him out of the window". :) Dec 29, 2010 at 20:39
  • Beyond the question of what the class should represent, what does an instance represent? If one regards SeesFood as a characteristic of DogEyes, Bark as something done by a DogVoice, and Eat as something done by a DogMouth, then logic like if (dog.SeesFood) dog.Eat(); else dog.Bark(); will become if (eyes.SeesFood) mouth.Eat(); else voice.Bark();, losing any sense of identity that eyes, mouth, and voice are all connected to a single entitity.
    – supercat
    Mar 25, 2014 at 19:20
  • @supercat it's a fair point, though context is important. If the code you mention is within the Dog class, then it's probably Dog-related. If not, then you would probably end up with something like myDog.Eyes.SeesFood rather than just eyes.SeesFood. Another possibility is that Dog exposes the ISee interface which demands the Dog.Eyes property and the SeesFood method.
    – JohnL
    Apr 1, 2014 at 13:32
  • @JohnL: If the actual mechanics of seeing are handled by a dog's eyes, in essentially the same way as by a cat's or a zebra's, then it may make sense to have the mechanics handled by an Eye class, but a dog should "see" using its eyes, rather than merely having eyes that can see. A dog isn't an eye, but nor is it simply an eye-holder. It is a "thing that can [at least try to] see", and should be described via interface as being such. Even a blind dog can be asked if it sees food; it won't be very useful, since the dog will always say "no", but there's no harm in asking.
    – supercat
    Apr 1, 2014 at 18:04
  • Then you'd use the ISee interface like I describe in my comment.
    – JohnL
    Apr 2, 2014 at 9:16
1

A class should do one thing when viewed at its own level of abstraction. It will doubtless do many things at a less abstract level. This is how classes work to make programs more maintainable: they hide implementation details if you don't need to examine them closely.

I use class names as a test for this. If I can't give a class a fairly short descriptive name, or if that name has a word like "And" in it, I'm probably violating the Single Responsibility Principle.

In my experience, it's easier to keep this principle at the lower levels, where things are more concrete.

1

Something to keep in mind: SRP, applied correctly, is useful. On the other hand, SRP, badly misinterpreted, is a recipe for total disaster (a post here recently: "We started using SRP. We have gone from 70 classes to 700. Is that normal?").

So if you find your self in a situation where you think that SRP wants you to do something stupid, then stop and think about how you might have misinterpreted SRP.

0

It's about having one unique rôle.

Each class should be resumed by a role name. A role is in fact a (set of) verb(s) associated with a context.

For example :

File provide a file's access. FileManager manage File objects.

Resource hold data for one resource from a File. ResourceManager hold and provide all Resources.

Here you can see that some verbs like "manage" imply a set of other verbs. Verbs alone are better thought as functions than classes, most of the time. If the verb imply too much actions that have their own common context, then it should be a class in itself.

So, the idea is only to let you have a simple idea of what does the class by defining a unique role, that might be the agregate of several sub-role (performed by member objects or other objects).

I often build Manager classes that have several different other classes in it. Like a Factory, a Registry, etc. See a Manager class like some kind of group chief, a orchestra chief that guide other peoples to work together to achieve a high level idea. He have one role, but imply working with other unique roles inside. You can also see it like how a company is organized : a CEO isn't a productive one on the pure productivity level, but if he is not there, then nothing can work correctly together. That's his role.

When you design, identify unique roles. And for each role, again see if it can't be cut in several other roles. That way, if you need to simlpy change the way your Manager build objects, simply change the Factory and go with peace in mind.

0

How would you describe what "one thing" means? What are some concrete signs that a class really does more than "one thing"?

If a class has things (methods or fields) which don't reference each other, it does more than "one thing". This is what Cohesion means, and what SRP tried to restate/reuse.

The point is more subtle of course, and it has (as everything) gray areas, but it way more usable than any definitions of SRP so far.

-1

The SRP is not just about dividing classes up, but also about delegating functionality.

In the Dog example used above, don't use SRP as justification to have 3 separate classes such as DogBarker, DogWalker, etc (low cohesion). Instead, look at the implementation of a class's methods and decide if they "know too much". You can still have dog.walk(), but probably the walk() method should delegate to another class the details of how walking is accomplished.

In effect we are allowing the Dog class to have one reason to change: because Dogs change. Of course, combining this with other SOLID principles you would Extend Dog for new functionality rather than changing Dog (open/closed). And you would Inject your dependencies such as IMove and IEat. And of course you would make these separate interfaces (interface segregation). Dog would only change if we found a bug or if Dogs fundamentally changed (Liskov Sub, don't extend and then remove behavior).

The net effect of SOLID is that we get to write new code more often than we have to modify existing code, and that is a big win.

-1

It all depends on the definition of responsibility and how that definition is going to impact maintenance of your code. Everything boils down to one thing and that is how your design is going to help you in maintaining your code.

And Like someone said "Walking on water and designing software from given specs is easy, provided both are frozen" .

So, if we are defining responsibility in more concrete way, then we don't need to change it.

Sometimes responsibilities will be glaringly obvious, but sometimes it might be subtle and we need to judiciously decide.

Suppose, we add another responsibility to Dog class named catchThief(). Now it might be leading towards an additional different responsibility. Tomorrow, if the way Dog is catching thief has to be changed by Police Dept, then Dog class will have to be changed. It would be better to create another subclass in this case and name it ThiefCathcerDog. But in a different view, if we are sure that it is not going to change in any circumstance, or the way catchThief has been implemented is dependent on some external parameter, then it is perfectly ok to have this responsibility. If responsibilities are not strikingly odd then we have to decide it judiciously based on the use case.

-1

"One reason to change" depends on who is using the system. Make sure you have you use cases for each actor, and make a list of most likely changes, for each possible change of use case make sure only one class would be affected by that change. If you are adding a entirely new use case, make sure you would only need to extend a class to do so.

7
  • 3
    One reason to change has nothing to do with the number of use cases, actors, or how likely a change is. You should not be making a list of likely changes. It is correct that one change should only impact one class. Being able to extend a class to accommodate that change is good, but that's the open closed principle, not SRP. Jul 3, 2016 at 4:53
  • Why should we not be making a list of most likely changes? Speculative design? Dec 3, 2016 at 21:24
  • 1
    because it won't help, won't be complete, and there are more effective ways to deal with change than to try to predict it. Just expect it. Isolate decisions and the impact will be minimal. Dec 3, 2016 at 22:01
  • Okay, I understand, it violates the "your not gona need it" principle. Dec 3, 2016 at 22:12
  • Actually yagni can be pushed to far as well. It's really meant to keep you from implementing speculative use cases. Not to keep you from isolating an implemented use case. Dec 3, 2016 at 22:29
-1

I think all answers I have read so far are missing the point, just restating what has been said without touching the motive for the principle.

It is all about maintenance. With any code written it is to be expected that sometime, something will need to be changed. As a programmer you should anticipate that. When that time has come you want things to be coded in a way that makes it possible to change one thing without breaking something else. This is what the S in SOLID is about.

Forget object orientation for a moment, this is way more fundamental. It is about integration versus modularisation.

Sometimes you want things to be integrated. Like computer chips. It allows you to produce lots of them cheap, providing a lot of functionality. What if 1 transistor fails? It renders the whole chip with millions of components useless, you can throw it away. Is that a problem? Not really. Chances anything will break are small, the chip is cheap and can be easily replaced, you will never in its lifetime feel the need to change the inner workings.

Now consider a car. You can also regard this as one thing, like the chip. But when a tire is worn out you don't want to toss the car. So it is a good thing that a tire can be replaced for relatively little money. It is also a good thing that the replacement of the tire will not impact your storage space or brake function or the side the car will go when you pull the steering wheel to the right.

Now we are getting somewhere.

Software systems are more like cars than like computer chips. They are not cheap and there will be needs for modification. Violating the SRP is like integrating parts you do not want to be integrated because it will make maintenance troublesome in the sense that you will have a hard time making a desired change without changing something else you do not want to change.

This is typically how it goes: In the original scenario A and B are used as a tandem, both are needed to achieve some goal. So A and B are implemented as such, in one piece of code. Later, a new feature is implemented that just needs B. The existing code is not usable because it also does A.

Separating responsibilities typically means some extra work and will initially result in some extra code. In the long run though this will always prove to be a good investment.

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