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

8
  • 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 3: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 3: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 3: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 7: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 9:57
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.

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