24

In the SRP, a 'responsibility' is usually described as 'a reason to change', so that each class (or object?) should have only one reason someone should have to go in there and change it.

But if you take this to the extreme fine-grain you could say that an object adding two numbers together is a responsibility and a possible reason to change. Therefore the object should contain no other logic, because it would produce another reason for change.

I'm curious if there is anyone out there that has any strategies for 'scoping', the single-responsibility principle that's slightly less objective?

Péter Török
  • 109,525
  • 28
  • 259
  • 326
Mark Rogers
  • 90,043
  • 18
  • 79
  • 129

6 Answers6

29

it comes down to the context of what you are modeling. I've done some extensive writing and presenting on the SOLID principles and I specifically address your question in my discussions of Single Responsibility.

The following first appeared in the Jan/Feb 2010 issue of Code Magazine, and is available online at "S.O.L.I.D. Software Development, One Step at a Time"


The Single Responsibility Principle says that a class should have one, and only one, reason to change.

This may seem counter-intuitive at first. Wouldn’t it be easier to say that a class should only have one reason to exist? Actually, no-one reason to exist could very easily be taken to an extreme that would cause more harm than good. If you take it to that extreme and build classes that have one reason to exist, you may end up with only one method per class. This would cause a large sprawl of classes for even the most simple of processes, causing the system to be difficult to understand and difficult to change.

The reason that a class should have one reason to change, instead of one reason to exist, is the business context in which you are building the system. Even if two concepts are logically different, the business context in which they are needed may necessitate them becoming one and the same. The key point of deciding when a class should change is not based on a purely logical separation of concepts, but rather the business’s perception of the concept. When the business perception and context has changed, then you have a reason to change the class. To understand what responsibilities a single class should have, you need to first understand what concept should be encapsulated by that class and where you expect the implementation details of that concept to change.

Consider an engine in a car, for example. Do you care about the inner working of the engine? Do you care that you have a specific size of piston, camshaft, fuel injector, etc? Or, do you only care that the engine operates as expected when you get in the car? The answer, of course, depends entirely on the context in which you need to use the engine.

If you are a mechanic working in an auto shop, you probably care about the inner workings of the engine. You need to know the specific model, the various part sizes, and other specifications of the engine. If you don’t have this information available, you likely cannot service the engine appropriately. However, if you are an average everyday person that only needs transportation from point A to point B, you will likely not need that level of information. The notion of the individual pistons, spark plugs, pulleys, belts, etc., is almost meaningless to you. You only care that the car you are driving has an engine and that it performs correctly.

The engine example drives straight to the heart of the Single Responsibility Principle. The contexts of driving the car vs. servicing the engine provide two different notions of what should and should not be a single concept-a reason for change. In the context of servicing the engine, every individual part needs to be separate. You need to code them as single classes and ensure they are all up to their individual specifications. In the context of driving a car, though, the engine is a single concept that does not need to be broken down any further. You would likely have a single class called Engine, in this case. In either case, the context has determined what the appropriate separation of responsibilities is.

Mark Rogers
  • 90,043
  • 18
  • 79
  • 129
Derick Bailey
  • 69,055
  • 21
  • 193
  • 207
  • 5
    Very well explained. As in all things in software design the context is key. – Chris Nicola Mar 17 '10 at 23:13
  • Responsibility has also different level of granularity (Context). E.g. Class is likely mapped to domain object, such as Product, Order, etc. The class of Product has only one reason to change that the domain object of Product is changed. May more properties to the product need to add. – Jacky Jan 09 '13 at 08:07
  • @Derick very beautiful article which makes me more clear about SOLID ? Thanks a lot. – Jacky Jan 09 '13 at 09:51
  • I realize this is an old topic, but I asked as similar question recently. Does this imply that an engine class would be a composition of individual objects for each of the internals? When you change the internals the change is confined to that part's class and when you use the engine the interface is just that of the engine? – Ryan Oct 14 '15 at 17:21
  • Ryan - assuming you need to model the internals of the engine, yes. The real question, though, is whether or not you need to model the internals of the engine. – Derick Bailey Oct 15 '15 at 14:29
  • Could you please help me with this question as I have bit problem following SRP : https://stackoverflow.com/questions/56017036/how-to-hide-logic-behind-a-class-to-improve-readability-of-method-and-refactor-c – ILoveStackoverflow May 10 '19 at 11:10
4

I tend to think in term of "velocity of change" of the business requirements rather than "reason to change" .

The question is indeed how likely stuffs will change together, not whether they could change or not.

The difference is subtle, but helps me. Let's consider the example on wikipedia about the reporting engine:

  • if the likelihood that the content and the template of the report change at the same time is high, it can be one component because they are apparently related. (It can also be two)

  • but if the likelihood that the content change without the template is important, then it must be two components, because they are not related. (Would be dangerous to have one)

But I know that's a personal interpretation of the SRP.

Also, a second technique that I like is: "Describe your class in one sentence". It usually helps me to identify if there is a clear responsibility or not.

ewernli
  • 36,434
  • 4
  • 82
  • 119
  • +1, interesting answer, that's kind of how I interpreted it originialy, but the wording of the principal seems a little vague. The principal seems to constantly require replacing of the word 'responsibility' with various phrases. – Mark Rogers Mar 16 '10 at 16:52
  • 4
    I've added a second trick that I like. I still completely agree that it's vague. But isn't it the case with any design *principle*? It's not a formula nor a recipe. – ewernli Mar 16 '10 at 17:17
  • Could you please help me with this question as I have bit problem following SRP : https://stackoverflow.com/questions/56017036/how-to-hide-logic-behind-a-class-to-improve-readability-of-method-and-refactor-c – ILoveStackoverflow May 10 '19 at 11:11
4

I don't see performing a task like adding two numbers together as a responsibility. Responsibilities come in different shapes and sizes but they certainly should be seen as something larger than performing a single function.

To understand this better, it is probably helpful to clearly differentiate between what a class is responsible for and what a method does. A method should "do only one thing" (e.g. add two numbers, though for most purposes '+' is a method that does that already) while a class should present a single clear "responsibility" to it's consumers. It's responsibility is at a much higher level than a method.

A class like Repository has a clear and singular responsibility. It has multiple methods like Save and Load, but a clear responsibility to provide persistence support for Person entities. A class may also co-ordinate and/or abstract the responsibilities of dependent classes, again presenting this as a single responsibility to other consuming classes.

The bottom line is if the application of SRP is leading to single-method classes who's whole purpose seems to be just to wrap the functionality of that method in a class then SRP is not being applied correctly.

Chris Nicola
  • 13,166
  • 6
  • 44
  • 61
  • But it doesn't really provide an answer to the question "How can I identify the responsibility of a class?" (my answer neither, btw) – ewernli Mar 16 '10 at 17:20
  • 2
    Well I guess that is the nature of software design, it does require some level of intuition and (not so) common sense. If we could just apply some arbitrary rule to achieve SRP then it wouldn't really be design and there would probably be a ReSharper SRP short-cut ;P. – Chris Nicola Mar 16 '10 at 17:38
  • Also, I think it is important to note that SOLID is specifically about 'principles' and not 'rules'. – Chris Nicola Mar 16 '10 at 17:39
  • +1 Good points, but it seems like your saying that it's the duty of a principal is to be vague, and not like a rule. A principal could be extremely rule-like and still be too difficult to be conceptualize into an automated refactoring system. Though obviously SRP could never be a completely automated refactoring tool. – Mark Rogers Mar 16 '10 at 18:14
  • 1
    True, I definitely don't mean a principle is meant to be vague, but it is typically more comprehensive than a basic rule is. It is meant to provide guidance, but not necessarily to express in exact terms how something must be done. – Chris Nicola Mar 16 '10 at 20:36
  • I wonder if misapplication of SOLID principles can in fact just be another new kind of "code smell". – Warren P Apr 15 '10 at 20:36
  • I think blindly applying them or applying them in isolation definitely can be. SOLID in the end encompasses the concept of good OOP API designs and is very much about designing code constructs that are easy for others to understand, use and extend. This is very hard to do well and takes time to get right. It can't be boiled down to a flow chart. – Chris Nicola Sep 12 '13 at 18:15
3

@Derick bailey: nice explanation
Some additions:
It is totally acceptable that application of SRP is contextual base.
The question still remains: are there any objective ways to define if a given class violates SRP ?

Some design contexts are quite obvious ( like the car example by Derick ) but otherwise contexts in which a class's behaviour has to defined remains fuzzy many-a-times.

For such cases, it might well be helpful if the fuzzy class behaviour is analysed by splitting it's responsibilities into different classes and then measuring the impact of new behavioural and structural relations that has emanated because of the split.

As soon the split is done, the reasons to keep the splitted responsibilities or to back-merge them into single responsibility becomes obvious at once.

I have applied this approach and which has lead good results for me.

But my search to look for 'objective ways of defining a class responsibility' still continues.

aknon
  • 1,348
  • 3
  • 16
  • 28
3

A simple rule of thumb I use is that: the level or grainularity of responsibility should match the level or grainularity of the "entity" in question. Obviously the purpose of a method will always be more precise than that of a class, or service, or component.

A good strategiy for evaluating the level of responsibility can be to use an appropriate metaphor. If you can relate what you are doing to something that exists in the real world it can help give you another view of the problem you're trying to solve - including being able to identify appropriate levels of abstraction and responsibility.

Adrian K
  • 7,874
  • 3
  • 30
  • 53
2

I respectful don't agree when Chris Nicola's above says that "a class should presents a single clear "responsibility" to it's consumers

I think SRP is about having a good design inside the class, not class' customers.

To me it's not very clear what a responsability is, and the prove is the number of questions that this concept arises.

"single reason to change"

or

"if the description contains the word "and" then it needs to be split"

leads to the question: where is the limit? At the end, any class with 2 public methods has 2 reasons to change, isn't it?

For me, the true SRP leads to the Facade pattern, where you have a class that simply delegades the calls to other classes

For example:

class Modem
  send()
  receive()

Refactors to ==>

class ModemSender
class ModelReceiver

+

class Modem
  send() -> ModemSender.send()
  receive()  -> ModemReceiver.receive()

Opinions are wellcome

ejaenv
  • 1,567
  • 18
  • 24
  • I find the modem case rather interesting, and tend to think that from a user-side perspective it would be better handled as a set of interfaces: `ITransmitStream`, `IReceiveStream`, `ITransmitSocket`, `IReceiveSocket`, `ITransmitReceiveSocket`, etc. because there are situations where one might wish to separate the transmit and receive aspects of the device (e.g. one could splice a serial cable to hook a barcode reader and printer up to the same port) but there are other cases where it's important that the two halves of a device be kept together. – supercat Dec 23 '11 at 15:42
  • This is why SOLID is more than just SRP. For example something like "open closed" is more relevant to the internal vs. external design of classes and APIs, but "responsibility" definitely speaks to what a class represents to it's consumers I don't see how you can interpret it any other way. Regardless of this though, I think that if you're trying to simplify SOLID down to simple rules you'll have a hard time. For example if SRP is just about facade classes then which classes actually do any work? – Chris Nicola Sep 12 '13 at 18:11