7

I learnt the SOLID principles back in 2011 and I try to use them in my day to day work.

However, I often find myself wandering if I am breaking the Single Responsibility principle. A class can have a number of responsibilities:

  1. Presentation
  2. Repository
  3. Service
  4. Domain
  5. Infrastructure etc.

I also see complaints if one class contains behaviour that should be in another e.g. OrderItem functionality in the order class. This is the one I struggle with.

I don't mix presentation with data access. I don't mix service with domain etc. However, I see questions on here that look ok to me and an answerer complains that the SRP is broken. How do you decide that a class has a single responsibility? I realise this may vary from domain to domain. What I am looking for is a methodology to help me decide if a class has a single responsibility (perhaps a set of questions).

blunova
  • 388
  • 1
  • 4
  • 16
w0051977
  • 7,031
  • 6
  • 59
  • 87
  • 1
    Being conscientious about it is the biggest battle. Few devs will agree on where the line is drawn. The key is to not think you'll get it right the first time, but when you start having to make changes to your code, are you having to change other things as well and can you redesign to avoid it? – JeffO Jun 27 '17 at 21:39
  • Possible duplicate of [How to determine if a class meets the single responsibility principle?](https://softwareengineering.stackexchange.com/questions/154723/how-to-determine-if-a-class-meets-the-single-responsibility-principle) – gnat Jun 27 '17 at 21:44
  • see also: [What is the real responsibility of a class?](https://softwareengineering.stackexchange.com/q/220230/31260) – gnat Jun 27 '17 at 21:45
  • 2
    Everyone defines "responsibility" differently, and this is the reason this guideline is mostly worthless. – Frank Hileman Jun 27 '17 at 22:17
  • @JeffO, Having to change many things because of a change in requirement is not a problem of SRP, but of coupling. Having to change the same code again and again for new and different reasons, that is a problem of SRP. – Chris Wohlert Jun 28 '17 at 06:25
  • 1
    Holy Alan Turing, Batman! Single Responsibility Principle is "mostly worthless"? Nay, nay! It is the most important! SRP is the invisible hand that is at the core of well written, maintainable OO code. The more my classes (and methods!) are like Popeye the Sailor the better: "I yam what I yam and that's all what I yam". In my experience, the absence of SRP is due in more or less degree to the attitude "that's too small to be a separate class." So sad because SRP is the PFM that makes classes and methods clear, concise, simple, and beautiful. – radarbob Jun 28 '17 at 19:39
  • @radarbob Whilst this is not the space to make such an argument, consider how we create higher level abstractions layered over lower level abstractions: by combining responsibilities. One can say the higher level abstraction has only a single responsibility only when viewed at the higher level. Disputes about what is a responsibility make the SRP useless in practice. There is no way to precisely define it. – Frank Hileman Jun 28 '17 at 23:06
  • It's a principle. That's all I have to say about that. – radarbob Jun 28 '17 at 23:23
  • @radarbob i can't argue with that! I would love to see your mathematical definition of the so-called "principle". – Frank Hileman Jun 29 '17 at 18:00
  • @FrankHileman - I don't think it's reasonable to disparage a principle on the basis that a lot of people don't understand what was clearly described in the [original article in which it was first suggested](https://drive.google.com/file/d/0ByOwmqah_nuGNHEtcU5OekdDMkk/view). If other people use a different definition of "responsibility", that's because they've misunderstood the principle, not because the principle is in some way deficient. (Edit: on examination, that's not the original version, but still -- the original definition *did* contain the same or at least a similar description) – Jules Jul 07 '17 at 00:49
  • 1
    @Jules The definition is imprecise in the original document as well. There is no way to define it in a precise way, and even the original author acknowledges the difficulty in doing so. Labeling it a "principle" was an error; it is not a general principle, and has inherent contradictions, as pointed out above. The problem with this "principle" is it attempts to be general and precise while dealing with fundamentally human concepts. The real goal of the guideline was to avoid creating classes containing unrelated data and behavior. That cannot be easily defined. – Frank Hileman Jul 07 '17 at 01:08
  • Quit pulling generic responsibilities out of a hat and declaring a class (or method) violates SRP. It's about your code, not framework-y. One way to learn it is backwards. The first time you have to rip apart a method or class to separate code, maybe it's an SRP thing. The first time you change an output stream and have to alter some `AddStuff` method, maybe that's an SRP thing. The first time you rewrite the hell out of the code because calling some business function "belonging" to another class requires an instantiated HTTP context, maybe that's an SRP thing. – radarbob Jul 31 '18 at 00:43
  • **cont:** Learn it forwards: Write `Add()` method that also prints to screen. You know that is an SRP violation. Like porn, you can't define it but you know it when you see it. SRP means thinking and building modularly. Encapsulation, abstraction, etc. - OO is just fancy modularity. Think about functional relationships at hand when coding; do not pull generic responsibilities out of a hat. And for cryin' out loud do not spread around code based on LOC. Good modularity - e.g. good OO - is more or less SRP'ed by default and tends to yield small methods naturally. – radarbob Jul 31 '18 at 01:22

5 Answers5

15

When something has only one responsibility it should only have one reason to change.

In fact this is a big reason why we care about giving things only one responsibility. It contains the impact of changes.

Now this doesn't mean that inside every class you find only one method or one member variable. It means the methods inside should each isolate change from each other by having their own responsibilities. And the class as a whole should have a responsibility that encompasses them, and only them. Oh and the class should have a name that makes the methods you find inside no surprise at all.

So yes, if you wanted you could organize a single class that had a method responsible for presentation, one for the repository, one to provide a service, one that provided some domain functionality, and one that existed to satisfy some infrastructure requirement. That would be weird. But so long as the overall class existed to fulfill a single responsibility, like say CatVideo then all of those methods had better be about doing all of those things for a CatVideo.

In short, responsibilities can nest. It's OK to look inside something with a single responsibility and find other things that have single responsibilities.

What's not cool is one class trying to do the job of two classes. Or one function trying to to the job of two functions. Or one package trying to do the job of two packages. Whatever the thing of the moment is, telling me what it is responsible for shouldn't involve the word "and".

Now if some of the methods in CatVideo are about Cat and some about Video it may be that instead of a single responsibility you smushed together two responsibilities and really need two objects: Cat and Video. If these responsibilities can be teased apart, they should be. But if a CatVideo really is a single idea in this domain then it's fine as it is.

When responsibilities are nested well you can pluck one out without impacting others. If your CatVideo class has it's repository changed that should not mean that presentation has to change. If it does presentation was taking on some of repositories responsibilities.

SRP isn't only about classes. It applies to every abstraction you have in your language.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
7

The Single Responsibility Principle (SRP) is widely misunderstood, possibly due to a a misleading name.

The principle is about how code change. Not in the sense of state-changes during execution, but about how source code is edited over time in response to changing requirements.

The worst kind of system is the one where a small requirements change requires you to modify the code in numerous different places in the source, and where each change causes bugs and unexpected behavior to pop up in seemingly unrelated parts of the code.

It is easy to write code from scratch. The primary challenge of software design is to write code which can change and adapt over time. If you look at some code and decide you have to rewrite it from scratch to support some new functionality, then the code was not designed according to the SRP.

The SRP tells you to think about what may cause the code to change over time. For example - why is presentation separate from domain? Because it is likely the UI of an application will change independently of the domain logic. It happens all the time. So you partition the code into units and classes based on what may change independently.

So forget about "responsibilities" - the word is too nebulous on its own. Think about what requirements changes could make it necessary to change an object.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
4

This is a good question.

If I'm allowed to opine, I would say the language is key for modelling. The methodology you are looking for is hidden in your reading comprehension.

If the OOP is meant to be a paradigm for modelling the real world, a good question would be: What the real world is?

The real world is nothing else than our perception of this. And you guess what? Our perception is shaped by our language. In consequence, for modelling, we have to focus on the reading comprehension and to be careful with the language we use for describing the problem (the domain).

Paying attention to the language we can suppose the differents abstractions that come into play and how they collaborate with each other. I'm not saying anything new, I guess you are aware of the so famous Ubiquitous Language of Eric Evans.

To me, abstractions and responsibilities are correlated.

At this point, you might be interested in GRASP.

We can repeat the analysis of the language for each of the abstractions because each of them can be decomposed into different levels of abstractions. How many levels do we need varies from project to project, but usually, a rule of thumb is KISS.

In short, responsibilities can nest. It's OK to look inside something with a single responsibility and find other things that have single responsibilities.

CandiedOrange' answer

Regarding this process (composition/decomposition) it might interest you the top-down and bottom-up design strategies.

Take as an example a supply chain, where supplying is the top level of abstraction, while the different jobs all along the chain are lower levels of abstraction.

Top-level : Supply

Mid-level : Assembly -> Packaging -> Shipping -> Delivery

...

If we need improve our chain with a faster delivering process, we can address the solution changing only this responsibility without having to change the whole chain. But we can do It because we understand that delivering and shipping are different responsibilities.

When something has only one responsibility it should only have one reason to change.

CandiedOrange' answer

Do you need one more evidence of the importance of the language?

Oh and the class should have a name that makes the methods you find inside no surprise at all

CandiedOrange' answer

If you can provide your abstractions with coherent names or if the names of your abstractions can describe coherently what each of them are supposed to do, it's likely you have modelled right the responsibilities.

Laiv
  • 14,283
  • 1
  • 31
  • 69
0

To stick with your order example, once you recognize your order groups a bunch of order items, it means the grouping part is its responsibility and you should be careful with adding features that are not related to the grouping of items.

So you will not want your order to offer a total price. This is not related to maintaining the order item collection, it is a billing issue. Nor do you want the order to figure out if all items are in stock, this is a stock issue. You do want it to offer a way to enumerate items so other classes can iterate through them and retrieve the information relevant to them. And you do want to limit the maximum number of items it should allow for a single order, if that were to be limited. Adding items, removing items, those are core responsibilities. Ideally the order should not know what an order item is or what it has to offer.

So what makes the order more than a list then? It is related to a client. It has a date and time for placement and cancelation. These are native properties, they pertain directly to the order itself. Do not be tempted to put all sorts of consolidated order item information on the order interface.

Then how would you calculate that total price? You could have an invoice class that takes an order as an argument to its constructor. The amount to be paid for the order would be an appropriate value for the invoice because that deals with money to be paid by the client.

For the availability of items, you could have a stock class that has a method GetItemAvailabily, that takes an order item as an argument. A OrderInformationProducer class may take an order, feed its items to Stock.GetAvailability and produce an order information object that tells the client what he may expect.

The above example is just to illustrate a strict separation of concerns. I never made an order handling system so it may be too simple or impractical. I hope it does convey the idea of SRP.

Martin Maat
  • 18,218
  • 3
  • 30
  • 57
-3

SOLID principles are a good starting point to consider how to design the architecture of a system, but it does contain a complete set of all the principles that should be assessed by a software architect.

Just as an example, SOLID does not include the principle of Separation of Concerns (SoC), which happens to be a rather important design principle, and the SRP principle in SOLID neither contains nor supersedes the SoC principle.

The SoC principle gives us a lot of input regarding how to organize code in different software layers, and what types of responsibilities should be grouped together in what layer.

As such, it is only at the business logic layer where business logic code should reside: this is the only layer where business objects or domain objects really exist and make sense.

At the presentation layer, only presentation logic should reside.

At the data access layer, only basic data access logic should reside: here is where the mapping between the object model and the data model should happen.

But we need to have a means to transfer the state of business objects between the layers: that is what Data Transfer Objects (DTOs) do!

In a sense, DTOs are like a cross-cutting concern, because they are present in all layers: DTOs are not business objects or domain objects, as their only (SRP) responsibility is to transfer the state of domain objects to the rest of the layers.

Regarding your doubts on the Order class and the OrderItem class, you have to realize how composite objects work: any given instance of the OrderItem class has no reason to exist outside an instance of the Order class, which is a composite class, and by the same token, any given instance of the Order class could not exist without a collection property of OrderItems, so, actually, there is not room for the confusion: they only exist as separate classes only in the source code, but they have no real meaning as separate classes in runtime.

I hope this small introduction is helpful enough to answer your questions and doubts.

Kind regards, GEN

GEN
  • 39
  • 1
  • 2
    I think Separation of Concerns follows from the single responsibility principle – Batavia Jun 27 '17 at 20:33
  • IMHO, that is not correct, as SRP only states that any given class should have a single and sharply scoped responsibilty, so, it says nothing about how said class should be grouped with other classes. This is what SoC says: classes with similar concerns should be grouped in the same layer. – GEN Jun 27 '17 at 20:46
  • 1
    Not all applications have nor should have separate business, presentation, and data layers, so framing the answer in those terms is a minefield of confusion. – whatsisname Jun 27 '17 at 20:47
  • Could you give some real life example (not trivial examples) where it would make sense to design a solution without SoC? – GEN Jun 27 '17 at 20:52
  • 2
    I disagree that SOLID is a good starting point for anything, other than creating overly complex programs. Whilst each so called "principle" is well intentioned and has valid applications in the right circumstance, none of the "principles" are general enough to apply to every situation, which is how they tend to be promoted. – Frank Hileman Jun 27 '17 at 22:19
  • I find the claim that SRP neither contains nor supersedes SoC fascinating. I've never figured out how to do one without the other. It's like one says don't mix jelly beans of different colors and the other says to put jelly beans with the same color together. There may be a difference here but I struggle to see what it is. – candied_orange Jun 27 '17 at 23:39
  • If your solution has a small set of Quality Attributes (QAs) to cover, like say, only performance and security, well, there is not much pressure to worry about the subtleties in how SRP and SoC are not one and the same. But if your solution has to give proper cover to a wider set of QAs, like say, performance, security, scalability, high availability with 365 / 24/7 coverage, and maintainability, to name a few, well, you can't assume that SRP and SoC are one and the same. Kind regards, GEN – GEN Jun 28 '17 at 14:48
  • I can't understand why the answer is down voted. These are just opinions, even if the SOLID principles are widely adopted some developers are disagreeing on them. pls check this tweet from Dan North, this guy created BDD and many other great things https://twitter.com/tastapod/status/825094828526546944?lang=en Regarding SoC it is a very general principle and can be applied in many levels. The SRP can only be applied to a class. The problem I see about SRP is how to define the responsibility of a class? there is no rules to define that, that's why I think it is still a vague principle. – Mohamed Bouallegue Jun 29 '17 at 11:57
  • Well, how to identify the responsibilities of a given class has already been solved a while back by people like Kent Beck & Ward Cunningham in their paper for the OOPSLA conference in 1989, "A Laboratory for Teaching Object-Oriented Thinking", (http://www.inf.ufpr.br/andrey/ci162/docs/beckCunningham89.pdf), and Rebecca Wirfs-Brock: (http://www.wirfs-brock.com/PDFs/A_Brief-Tour-of-RDD.pdf). In a sense, Domain Driven Design is a natural evolution of Responsibility Driven Design. – GEN Jun 29 '17 at 15:13