Honestly, I think you're taking the concept of single responsibility a little too far. Getters and setters are incidental to the functioning of the class whether you do it by direct access to public members or use methods (or properties) to do it.
You're making the argument that getting and setting some member of the class is a separate responsibility and should therefore be moved elsewhere. Let's say we do that, and now you have classes called Config
and ConfigAccessor
. At this point, you now have an air gap between the two classes, because Config
has no interface to access its location
member. That makes it impossible to write ConfigAccessor
, and you're left with an immutable, write-once class that's of no use whatsoever. If you add some sort of interface to allow ConfigAccessor
to do its job, you'll find yourself with a recursive problem.
The SRP, like many other things in this field is a principle, not a hard-and-fast rule. That means you should be applying judgement to your situation instead of trying to follow it unconditionally. There's a line between being a purist and getting the job done, and when the former is preventing the latter, you're on the wrong side of it.
If I can critique your design a bit: If your Config
class is designed to be an interface between a configuration file stored on disk and your code, the last thing you want to do is change its location midstream. If you're changing the location
as a way of starting access to a different file, you should be destroying the old object and creating a new one. You didn't make clear whether you intend to store the contents of the file in the object. If you'd planned to use it as a way to inhale the contents of one configuration file and write it to another, consider using a method that clones the data into a new object that points at the new file.
Given some divergences between languages, this can be a tricky topic. Thus, I'm formulating the following commentaries in a way that tries to be as comprehensive as I can inside the realm of OO.
First of all, the so called "Single Responsibility Principle" is a reflex -- explicitly declared -- of the concept cohesion. Reading the literature of the time (around '70), people were (and still are) struggling to define what a module is, and how to construct them in a way that would preserve nice properties. So, they would say "here is a bunch of structures and procedures, I'll make a module out of them", but with no criteria as to why this set of arbitrary things are packaged together, the organization might end up making little sense -- little "cohesion". Hence, discussions on criteria emerged.
So, the first thing to note here is that, so far, the debate is around organization and related effects on maintenance and understandability (for little matter to a computer if a module "makes sense").
Then, someone else (mr. Martin) came in and applied the same thinking to the unit of a class as a criteria to use when thinking about what should or should not belong to it, promoting this criteria to a principle, the one being discussed here. The point he made was that "A class should have only one reason to change".
Well, we know from experience that many objects (and many classes) that appear to do "many things" have a very good reason for doing so. The undesirable case would be the classes that are bloated with functionality to the point of being impenetrable to maintenance, etc. And to understand the latter is to see where mr. Martin was aiming at when he elaborated on the subject.
Of course, after reading what mr. Martin wrote, it should be clear these are criteria for direction and design to avoid problematic scenarios, not in any way to pursue any kind of compliance, let alone strong compliance, specially when "responsibility" is ill defined (and questions like "does this violates the principle?" are perfect examples of the widespread confusion). Thus, I find it unfortunate it is called a principle, misleading people into try to take it to the last consequences, where it would do no good. Mr. Martin himself discussed designs that "do more than one thing" that should probably be kept that way, since separating would yield worse results. Also, there are many known challenges regarding modularity (and this subject is a case of it), we are not at a point of having good answers even for some simple questions about it.
However, if we extrapolate these ideas, why would Object have a toString class? It's not a Car's or a Dog's responsibility to convert itself to a string, now is it?
Now, let me pause to say something here about toString
: there is a fundamental thing commonly neglected when one makes that transition of thought from modules to classes and reflect on what methods should belong to a class. And the thing is dynamic dispatch (aka, late binding, "polymorphism").
In a world with no "overriding methods", choosing between "obj.toString()" or "toString(obj)" is a matter of syntax preference alone. However, in a world where programmers can change the behavior of a program by adding a subclass with distinct implementation of an existing/overridden method, this choice is no more of taste: making a procedure a method also can make it a candidate for overriding, and the same might not be true for "free procedures" (languages that support multi-methods have a way out of this dichotomy). Consequently, it is no more a discussion on organization only, but on semantics as well. Finally, to which class the method is bound, also becomes an impacting decision (and in many cases, so far, we have little more than guidelines to help us decide where things belong, as non-obvious trade-offs emerge from different choices).
Finally, we are faced with languages that carry terrible design decisions, for instance, forcing one to create a class for every little bit of thing. Thus, what once was the canonical reason and main criteria for having objects (and in the class-land, therefore, classes) at all, which is, to have these "objects" that are kind of "behaviors that also behave like data", but protect their concrete representation (if any) from direct manipulation at all costs (and that's the main hint for what should be the interface of an object, from the point of view of its clients), gets blurred and confused.
Best Answer
The single responsibility might not be something that a single function can fulfill.
This class may break the single responsibility principle. Not because it has two functions, but if the code for
getX()
andgetY()
have to satisfy different stakeholders that may demand change. If Vice President Mr. X sends around a memo that all numbers shall be expressed as floating point numbers and Accounting Director Mrs. Y insists that all numbers her department reviews shall remain integers regardless of what Mr. X thinks well then this class had better have a single idea of who it's responsible to because things are about to get confusing.If SRP had been followed it would be clear if the Location class contributes to things Mr X and his group are exposed to. Make clear what the class is responsible to and you know which directive impacts this class. If they both impact this class then it was poorly designed to minimize the impact of change. "A class should only have one reason to change" doesn't mean the entire class can only ever do one little thing. It means I shouldn't be able to look at the class and say that both Mr X and Mrs Y have an interest in this class.
Other than things like that. No, multiple methods are fine. Just give it a name that makes clear what methods belong in the class and what ones don't.
Uncle Bob's SRP is more about Conway's Law than Curly's Law. Uncle Bob advocates applying Curly's Law (do one thing) to functions not classes. SRP cautions against mixing reasons to change together. Conway's Law says the system will follow how an organization's information flows. That leads to following SRP because you don't care about what you never hear about.
People keep wanting SRP to be about every reason to limit scope. There are more reasons to limit scope than SRP. I further limit scope by insisting the class be an abstraction that can take a name that ensures looking inside won't surprise you.
You can apply Curly's Law to classes. You're outside what Uncle Bob talks about but you can do it. Where you go wrong is when you start to think that means one function. That's like thinking a family should only have one child. Having more than one child doesn't stop it from being a family.
If you apply Curly's law to a class, everything in the class should be about a single unifying idea. That idea can be broad. The idea might be persistence. If some logging utility functions are in there, then they are clearly out of place. Doesn't matter if Mr X is the only one who cares about this code.
The classic principle to apply here is called Separation of Concerns. If you separate all your concerns it could be argued that what's left in any one place is one concern. That's what we called this idea before the 1991 movie City Slickers introduced us to the character Curly.
This is fine. It's just that what Uncle Bob calls a responsibility isn't a concern. A responsibility to him isn't something you focus on. It's something that can force you to change. You can focus on one concern and still create code that is responsible to different groups of people with different agendas.
Maybe you don't care about that. Fine. Thinking that holding to "do one thing" will solve all of your design woes shows a lack of imagination of what "one thing" can end up being. Another reason to limit scope is organization. You can nest many "one thing"s inside other "one thing"s until you have a junk drawer full of everything. I've talked about that before
Of course the classic OOP reason to limit scope is that the class has private fields in it and rather then use getters to share that data around, we put every method that needs that data in the class where they can use the data in private. Many find this too restrictive to use as a scope limiter because not every method that belongs together uses exactly the same fields. I like to ensure that whatever idea that brought the data together be the same idea that brought the methods together.
The functional way to look at this is that
a.f(x)
anda.g(x)
are simply fa(x) and ga(x). Not two functions but a continuum of pairs of functions that vary together. Thea
doesn't even have to have data in it. It could simply be how you know whichf
andg
implementation you're going to use. Functions that change together belong together. That's good old polymorphism.SRP is just one of many reasons to limit scope. It's a good one. But not the only one.