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).

  • 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? – gnat Jun 27 '17 at 21:44
  • 2
    Everyone defines "responsibility" differently, and this is the reason this guideline is mostly worthless. – Frank Hileman Jun 27 '17 at 22:17
  • 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
  • 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 7 '17 at 1:08
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.

  • I have quoted your answer from mine. Hope you don't mind. – Laiv Jun 27 '17 at 22:55
  • @laiv you have a good answer so I don't mind at all. You know you're making it hard for me to not upvote the competition. – candied_orange Jun 27 '17 at 23:28
  • It's ok. I'm not competitive :-) – Laiv Jun 28 '17 at 7:04
  • "responsibilities can nest." Bam! Now y'all have the mental framework for excellent SRPness. It is at once that simple and difficult. There is no rote formula. – radarbob Jun 29 '17 at 19:46
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.

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.

  • 1
    +1 for GRASP. I was already aware of it though. – w0051977 Jun 27 '17 at 22:58
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.

-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

  • 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

protected by gnat Jun 29 '17 at 6:15

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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