0

This is a related question and the OP appeared to go with extreme levels of granularity. The answers gave a good overview but I gather it's still somewhat down to personal interpretations/situations as to where to draw the line. I want to know from the examples below which one best represents SRP.

Example 1

Say you have a UserService class with the ff. methods

  • createUser()
  • updateUser()
  • deleteUser()

The UserService is only "responsible" for all things you can with with the Users. Is this a good or "minimum acceptable" implementation of SRP as it doesn't deal with Products, Shipping etc?

Example 2

Or, do you have to at least be one level granular like the ff. classes/methods:

  • CreateUserService->createUser()
  • UpdateUserService->updateUser()
  • deleteUserService->deleteUser()

This way CreateUserService is only responsible for creating a user, nothing else. CreateUserService may also contain other supporting methods to properly perform user creation e.g., it can have doesUserAlreadyExist() method to prevent duplicates. This seems to be a better approach.

Example 3

Or, do you still go another level deep and separate the supporting methods too:

  • CreateUserService->createUser()
  • UserExistService->doesUserAlreadyExist()

This further separates the responsibility of CreateUserService and UserExistService


My hunch tells me example #2 is the way to go but #3 isn't that far fetched. While #1 seems to be the minimum acceptable implementation.

Based on the above examples, which one is the best implementation of SRP? (or maybe there's some other implementation I missed)?

IMB
  • 323
  • 1
  • 10
  • Software design/architecture is about coping with the complexities of change, so you have to view all of these principles in that context. The kinds of changes that affect us (what people want and how we make it happen) jump between complicated (we can figure out via analysis, perhaps not easily, how to proceed) and complex (cause and effect not obvious, we must try things and learn on the go). As you work on a system, you'll find some patterns that are stable as it evolves, with changes happening within that "framework". E.g., certain parts of the codebase always change together. 1/3 – Filip Milovanović Jul 11 '19 at 03:56
  • SRP is about recognizing these axes of change, and then structuring your system to support them. E.g., you take the scattered code that changes together and design a class or a group of classes that are closely related (cohesivness), in a way that makes it possible for you to easily extend or replace parts for similar kinds of changes (loose coupling with client code). That's assuming you've established that there is a tendency for a certain kind of change (an axis of change, "responsibility"), with some elements to it that are common/stable, that you can base your abstractions on. 2/3 – Filip Milovanović Jul 11 '19 at 03:56
  • As for granularity: you try to find a balance between how you choose to represent stuff and what's workable; a model is no good if you can't work with it - and that involves things like readability, the way it's expressed, a shared understanding of concepts in a team, ease of use, runtime performance and other tech constraints, etc. P.S. "I gather it's still somewhat down to personal interpretations/situations" - yes, but that's *why* there are programmers - that's a big part of what you *do*. If it was all clear-cut, we'd probably be able to automate most of it. 3/3 – Filip Milovanović Jul 11 '19 at 03:57
  • @FilipMilovanović What you're saying and the duplicates that Robert Harvey posted further emphasizes that there's no clear cut answer. From all these reading, I think SRP's goal is to just minimize cascading code changes relative to what you or your team wants to handle. That said, I think it's safe to say my examples above are all valid SRP? It's just a matter of how much time you want to save in a future code change that may or may not happen. – IMB Jul 11 '19 at 07:16
  • Don't get me wrong - while there is a judgement call involved, it's not completely arbitrary. You will have to come up with what the different responsibilities are, and there's more then one option, but not all of them will work equally well for you, depending on how well they capture the real-world forces that drive the changes, and take into account other factors that affect your code. So it depends on the real-world problem you are dealing with, which is why you have to understand and study your system and the problem domain. Once you've defined the resp.s, you can identify violations. 1/3 – Filip Milovanović Jul 11 '19 at 09:57
  • Some things you'll design correctly from the start, but not all of them; essentially, if after a while the code is just hard to work with and stuff gets in your way, or it doesn't really work the way it should, then there's probably a better design, and you redesign incrementaly before it becomes a tangled mess of dependencies and clever hacks (= you learn on the go, prevent code rot). That said, there are some things for which you can normally assume separate responsibilities - e.g. UI representation vs underlying data (e.g. cosmetic tweaks of the UI), you don't want those mixed together. 2/3 – Filip Milovanović Jul 11 '19 at 09:57
  • As for your examples - let's say there could be circumstances that render each design valid, but the point is that a code snippet is (in general) not enough to tell; you need context (knowledge of the domain/system) to decide. BTW, unless you are fairly certain about something, you shouldn't design too eagerly for hypothetical future changes (YAGNI); instead, wait for 3+ cases that suggest a certain design, else you risk making the system rigid in the wrong way. (You can't support all possible changes at once; some aspects have to be rigid for other aspects to be flexible.) 3/3 – Filip Milovanović Jul 11 '19 at 09:57
  • @FilipMilovanović Thank you, I appreciate the comments. – IMB Jul 12 '19 at 08:20

1 Answers1

0

You misunderstand the word "responsibility". In example 1, the service has a responsibility to manage users. That is a single responsibility.

In the question that you referenced, the nightmare they experienced is because they misunderstood this concept entirely.

BobDalgleish
  • 4,644
  • 5
  • 18
  • 23