16

I am not looking for an opinion about semantics but simply for a case where having getters sensibly used is an actual impediment. Maybe it throws me into a never-ending spiral of relying on them, maybe the alternative is cleaner and handles getters automatically, etc. Something concrete.

I've heard all the arguments, I've heard that they're bad because they force you into treating objects as data sources, that they violate an object's "pure state" of "don't give out too much but be prepared to accept a lot".

But absolutely no sensible reason for why a getData is a bad thing, in fact, a few people argued that it's a lot about semantics, getters as fine per-se, but just don't name them getX, to me, this is at least funny.

What is one thing, without opinions, that will break if I use getters sensibly and for data that clearly the object's integrity doesn't break if it puts it out?

Of course that allowing a getter for a string that's used to encrypt something is beyond dumb, but I'm talking about data that your system needs to function. Maybe your data is pulled through a Provider from the object, but, still, the object still needs to allow the Provider to do a $provider[$object]->getData, there's no way around it.


Why I'm asking: To me, getters, when used sensibly and on data that is treated as "safe" are god-sent, 99% of my getters are used to identify the object, as in, I ask, through code Object, what is your name? Object, what is your identifier?, anyone working with an object should know these things about an object, because nearly everything about programming is identity and who else knows better what it is than the object itself? So I fail to see any real issues unless you're a purist.

I've looked at all the StackOverflow questions about "why getters / setters" are bad and though I agree that setters are really bad in 99% of the cases, getters don't have to be treated the same just because they rhyme.

A setter will compromise your object's identity and make it very hard to debug who's changing the data, but a getter is doing nothing.

coolpasta
  • 641
  • 5
  • 15
  • Why would setters be a bad thing? Of course, they are bad if your want to treat your objects as immutable but that's a whole different question. Many programming languages don't really use normal methods as getters/setters, they rather have a concept of properties. – Sulthan May 05 '19 at 09:53
  • Precise and concise, an extremely popular case: Create an object > Create a setter for a dependency object in this object > Create 4-5 objects that each set a different dependency object to our original object. Now you have 4-5 objects that, during the lifetime of your application, potentially **change a depdency of an object on the fly, virtually making you play roulette with your original object's internal data: you have no idea what changed what, how**. I saw the bad side of setters instantly and as for me, I'm never using them...for now :) My knowledge might update. – coolpasta May 05 '19 at 13:28
  • @Sulthan I'm talking about non-robust languages like PHP where I don't have return types (let alone complex return types). I would genuinely end up with a system where, as per interfaces everything is respected, but because of no return types, anything that relies on my original object's data has as high chances to crash as Kim K to get more botox in her face. – coolpasta May 05 '19 at 13:30
  • [This is a viewpoint you should strongly consider.](https://youtu.be/QM1iUe6IofM?t=60) I don't agree with everything it says or the way it puts everything (particularly, some of its "solutions" aren't helpful, imo), but it's very important to rethink the OO dogmas. They are not as practical as their advocates would have you believe. Anyone who writes practical OO code is not going to have adhered to them very strongly. They're going to have, at best, adhered to them in a very loose, targeted, and limited way that doesn't pervade every aspect of their code. – jpmc26 May 05 '19 at 22:28
  • 2
    My advice is forget most of the OO theory. Practice. Write a lot of code, and then maintain it. You'll learn far, far, far more about what works well and what doesn't from actually doing something and then going back to it a few months later. – jpmc26 May 05 '19 at 22:31
  • 1
    @jpmc26 What in the mother of .... I never actually realized that none of us actually do OOP correctly, I've always had this notion of encapsulation strictly relating to an object's fields, but the object itself is a state and is being freely shared. Genuinely everyone is doing OOP wrong, then, or rather, OOP is not possible if the paradigm is objects. I use OOP strictly to express how a system works and never treated it as the holy grail, nor SOLID. They're just tools I use mostly to define a reasonably well-expained (through implementation) of my concept. Any reads on if this is right? – coolpasta May 06 '19 at 01:24
  • 2
    --cont, The more I write, the more I realize is programming is all about being able to abstract as much as it's required in order to **be able to reason, to others, as well to you** about your codebase. Of course, the technical side of this is making sure you do the proper checks / set the right rules. In truth, whoever manages to make others understand their code better wins. Make sure it makes sense to anyone reading it, then make sure it doesn't fail by properly segmenting objects with interfaces, checks and switch to other paradigms when it's easier to reason with them: be flexible. – coolpasta May 06 '19 at 01:27
  • @coolpasta Yes, pretty much. I'm emphasizing that the OO mindset of thinking in terms of "object identity" and trying to have an object per state are impractical and ultimately more likely to mislead. The best code will avoid mutable state, and what state there is can't just be managed by "isolating" it into some specific object. What helps is minimizing how much you modify it at all and making sure it's easy to understand the modifications that happen. Using setters to constrain the states helps, but OO doesn't help with building readable code paths. – jpmc26 May 06 '19 at 02:06
  • Most programs will only need one or two objects as OO envisions them, and often these objects are outside of your own code. (E.g., the top level of a web framework like ASP.NET might arguably fit into what OO calls an object, but the controllers you write clearly don't since they should be stateless. There's no reason ASP.NET couldn't have implemented controllers as static methods that accept a request object.) They'll need many, many more methods, functions, and plain data objects to actually encode all the required logic, though, meaning that most code doesn't fit well into the OO paradigm. – jpmc26 May 06 '19 at 02:14
  • 4
    This seems like a strawman to me. I don't believe any serious developer who actually writes usable code would argue that using getters sensibly is a problem. The question implies getters can be used sensibly which further implies getters can be sensible. Are you looking for someone to say there is no sensible use of getters? You'll be waiting for a while. – JimmyJames May 07 '19 at 20:58
  • 2
    @coolpasta You're right. I don't really care if it's pure OOP. Just better. Give me a choice between broken code that can be understood by humans vs flawless CPU loving code that is indecipherable and Ill take the human friendly code every time. OOP is ONLY useful to me when it reduces the number of things I have to think about at any one time. That's the only reason I like it over straightforward procedural. Without that you're just making me jump through multiple source code files for no reason. – candied_orange May 08 '19 at 02:19
  • A flavor of OOP that worked well for me the last years is to differentiate between "data holders" and objects. Data holders don't do any real work and are made to provide simple access to the data. You don't need a getter there because you can just directly expose the fields (e.g. `adress.houseNumber`). Objects are then classes that _do_ do work; here you don't need getters because the work is done in methods and you get the results as return values from these (e.g. `Adress adress = adressFinder.Find(userInput);`). – R. Schmitz May 08 '19 at 12:51
  • @R.Schmitz if you're doing this in C# it's fine because you can easily upgrade to getters, to set a breakpoint to detect data moving, without having to change using code. However, Java is stuck with no way to upgrade to getters without breaking using code. This forces us to preemptively add getters before we're even using them. This can be even more critical when the boundary we're up against is a deployment boundary that keeps us from refactoring later. It's a huge YANGI violation but in Java it's your best option. See also [JavaBean convention](https://stackoverflow.com/a/21329056/1493294) – candied_orange May 09 '19 at 11:00
  • @candied_orange I have just never encountered the situation where I would need to add a getter for data (that's limited to my own experience of course). E.g. the example you give, data moving, doesn't happen in a data holder then; it happens inside an object. Plus I strongly prefer immutable data, so either I have a break-point-able constructor - or they are edge cases where changing data only happens in very few places in the code (literally only one in most of these cases). I should probably add that I'm writing applications - for libraries this would be different. – R. Schmitz May 09 '19 at 11:48
  • @R.Schmitz immutable argues against setters not getters. Encapsulation argues against getters. And yes, a library would be a boundary. So would a module that you want to independently deploy without recompiling everything else. Sometimes the boundary is because the code is coming from a different group of developers. Those boundaries only count though if they keep you from moving a method. – candied_orange May 09 '19 at 12:24
  • @candied_orange Oh, I wasn't saying that immutability means no getters, I was saying that with an immutable Java class, you can "_set a breakpoint to detect data moving, without having to change using code_". – R. Schmitz May 09 '19 at 13:01
  • 1
    @R.Schmitz Well if you're saying you can simply consider the movement of the reference the movement of the data that is reasonable when it's immutable. But again unless you're blessed with C# you're still painting yourself into a corner by doing this in Java. See [immutable type public final fields vs getter](https://stackoverflow.com/questions/6927763/immutable-type-public-final-fields-vs-getter) – candied_orange May 09 '19 at 13:16

6 Answers6

25

You can't write good code without getters.

The reason why isn't because getters don't break encapsulation, they do. It isn't because getters don't tempt people to not bother following OOP which would have them put methods with the data they act on. They do. No, you need getters because of boundaries.

The ideas of encapsulation and keeping methods together with the data they act on simply don't work when you run into a boundary that keeps you from moving a method and so forces you to move data.

It's really that simple. If you use getters when there is no boundary you end up having no real objects. Everything starts to tend to the procedural. Which works as well as it ever did.

True OOP isn't something you can spread everywhere. It only works within those boundaries.

Those boundaries aren't razor thin. They have code in them. That code can't be OOP. It can't be functional either. No this code has our ideals stripped from it so it can deal with harsh reality.

Michael Feathers called this code fascia after that white connective tissue that holds sections of an orange together.

This is a wonderful way to think about it. It explains why it's ok to have both kinds of code in the same code base. Without this perspective many new programmers cling to their ideals hard, then have their hearts broken and give up on these ideals when they hit their first boundary.

The ideals only work in their proper place. Don't give up on them just because they don't work everywhere. Use them where they work. That place is the juicy part that the fascia protects.

A simple example of a boundary is a collection. This holds something and has no idea what it is. How could a collection designer possibly move the behavioral functionality of the held object into the collection when they have no idea what it's going to be holding? You can't. You're up against a boundary. Which is why collections have getters.

Now if you did know, you could move that behavior, and avoid moving state. When you do know, you should. You just don't always know.

Some people just call this being pragmatic. And it is. But it's nice to know why we have to be pragmatic.


You've expressed that you don't want to hear semantic arguments and seem to be advocating putting "sensible getters" everywhere. You're asking for this idea to be challenged. I think I can show the idea has problems with the way you've framed it. But it also think I know where you're coming from because I've been there.

If you want getters everywhere look at Python. There is no private keyword. Yet Python does OOP just fine. How? They use a semantic trick. They name anything meant to be private with a leading underscore. You're even allowed to read from it provided you take responsibility for doing so. "We're all adults here", they often say.

So what's the difference between that and just putting getters on everything in Java or C#? Sorry but it's semantics. Pythons underscore convention clearly signals to you that you're poking around behind the employees only door. Slap getters on everything and you loose that signal. With reflection you could have stripped off the private anyway and still not have lost the semantic signal. There simply isn't a structural argument to be made here.

So what we're left with is the job of deciding where to hang the "employees only" sign. What should be considered private? You call that "sensible getters". As I've said, the best justification for a getter is a boundary that forces us away from our ideals. That shouldn't result in getters on everything. When it does result in a getter you should consider moving the behavior further into the juicy bit where you can protect it.

This separation has given rise to a few terms. A Data Transfer Object or DTO, holds no behavior. The only methods are getters and sometimes setters, sometimes a constructor. This name is unfortunate because it's not a true object at all. The getters and setters are really just debugging code that give you a place to set a breakpoint. If it wasn't for that need they'd just be a pile of public fields. In C++ we used to call them structs. The only difference they had from a C++ class was they defaulted to public.

DTO's are nice because you can throw them over a boundary wall and keep your other methods safely in a nice juicy behavior object. A true object. With no getters to violate it's encapsulation. My behavior objects may eat DTO's by using them as Parameter Objects. Sometimes I have to make a defensive copy of it to prevent shared mutable state. I don't spread mutable DTO's around inside the juicy part within the boundary. I encapsulate them. I hide them. And when I finally run into a new boundary I spin up a new DTO and throw it over the wall thus making it someone else's problem.

But you want to provide getters that express identity. Well congrats you've found a boundry. Entities have an identity that goes beyond their reference. That is, beyond their memory address. So it has to be stored somewhere. And something has to be able to refer to this thing by it's identity. A getter that expresses identity is perfectly reasonable. A pile of code that uses that getter to make decisions that the Entity could have made itself is not.

In the end it's not the existence of getters that is wrong. They are far better than public fields. What's bad is when they are used to pretend you're being Object Oriented when you're not. Getters are good. Being Object Oriented is good. Getters are not Object Oriented. Use getters to carve out a safe place to be Object Oriented.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • This is exactly why I asked and I'm not sure if my conversation is visible with Robert, I was also trying to find out a specific case where it clearly hurts me to use "sensible getters". I, for myself **disagree** with setters because at least for myself, it's very hard to attribute ownership to an action like this, I used to use setters in everything and by the time I realized that even 1 setter, when affected by too many objects kills me...I had to re-write everything. Sensible use of getters, after exhaustive testing for so long, seriously have no down-sides if you're not a purist. – coolpasta May 05 '19 at 13:23
  • I do understand how they violate encapsulation, but what I'd say, for lack of better words is that **the infraction is so small, if it doesn't happen often, that the benefits far outweigh the risks**. – coolpasta May 05 '19 at 13:25
  • 1
    @coolpasta you can create data owner objects. Data owner objects are data objects, but they are designed and named so that they (clearly) own the data. Of course these objects will have getters. – rwong May 05 '19 at 14:47
  • 1
    @rwong What do you mean by `clearly`? If data is passed to me from something, **by value**, clearly, my object now owns the data, but by semantics, it yet doesn't, at this point, it simply got the data from someone else. It then must perform operations to transform that data, making it its own, the moment the data is touched, you must make semantic changes: You got data named as `$data_from_request` and now you operated on it? Name it `$changed_data`. You wish to expose this data to others? Create a getter `getChangedRequestData`, then, ownership is clearly set. Or is it? – coolpasta May 05 '19 at 15:41
  • 1
    "You can't write good code without getters." This is utterly wrong, as is the notion that boundaries inherently need getters. This is not pragmatism, just laziness. It is quite a bit easier to just throw data over the fence and be done with it, thinking about use-cases and designing a good behavioral api *is* hard. – Robert Bräutigam May 05 '19 at 17:58
  • @RobertBräutigam There are getters and setters on commands and queries. Anytime one needs to access the data, getters and setters have to be used. The lesson here is not to confuse getters with queries, and not to confuse setters with commands. – rwong May 05 '19 at 18:18
  • @candied_orange `Collection.forEach` ? – Robert Bräutigam May 05 '19 at 18:22
  • @candied_orange in that case, it would not be a typical "getter", but would be a callback function "call_with". Caller provides a function (lambda) accepting an item as argument; data owner class would call the function (lambda) passing in the item. This pattern is used whenever the data owner class needs to perform locking. But, that is a getter in disguise. "for_each" is a multi-item variation of "call_with". For example, "for_each_with_lock" can provide the additional guarantee that the entire iteration occurs while the data owner object holds on to the lock continuously without gap. – rwong May 05 '19 at 18:23
  • Then all you've done is find a way to move the method after all. If you can do that fine! But remember how things worked out for CORBA before you invest to much in Remote Procedure Calls. I'd rather throw a DTO over a wall to an adapter that knows it's talking to a RESTful interface so I don't have to know. – candied_orange May 05 '19 at 18:32
  • Sorry, I also have a problem with the "You can't write good code without getters." part, getters are fairly new to programming and there's plenty of good code out there that predates that trend. It kinda sounds like you're saying no code written in a non-OOP language can be good, too (I hope that's not what you're saying). I'd argue you can even write good OOP code without getters; Alan Kay never thought in terms of data, for example, he thought of objects as receiving messages from each other. – jrh May 05 '19 at 20:35
  • 2
  • @jrh I won't argue against that perspective. Everything can be put into some context that will make it absurd and you've successfully done that. What I'm arguing is that insisting that every line of code be OOP cripples you in most supposedly OOP languages. Smalltalk avoided facia by making you do construction in a GUI. Some try to use the Spring library to solve the same problem. What I'm trying to show is that even an OOP purist will write non OOP code on some platforms when trying to create a safe place for the OOP code. Doing that, for that reason, is no sin. – candied_orange May 06 '19 at 00:59
  • I think we might have read this Q/A pair two different ways, sorry for the mixup – jrh May 06 '19 at 01:45
  • @jrh Well I haven't heard from coolpasta directly but I hope what I'm saying is helpful. In addition to defending getters "used sensibly" I'm also trying to keep them from spreading to where they're not needed. I'm trying to find a way to take the religious ideology out of this issue by illustrating the actual need. – candied_orange May 06 '19 at 02:02
  • I have decided to select this as the answer because both the answer and the comments provide very concise arguing points for getters and I've learned a lot and changed my software due to it. I can't for sure whether or not this is the RIGHT answer but as far as my tests go, it is. – coolpasta Jun 04 '19 at 09:14
13

Getters violate the Hollywood Principle ("Don't call us, we'll call you")

The Hollywood Principle (aka Inversion of Control) states that you don't call into library code to get things done; rather, the framework calls your code. Because the framework controls things, broadcasting its internal state to its clients is not necessary. You don't need to know.

In its most insidious form, violating the Hollywood Principle means that you're using a getter to obtain information about the state of a class, and then making decisions about which methods to call on that class based on the value that you obtain. it's violation of encapsulation at its finest.

Using a getter implies that you need that value, when you actually don't.

You might actually need that performance improvement

In extreme cases of lightweight objects that must have the maximum possible performance, it's possible (though extremely unlikely) that you can't pay the very small performance penalty that a getter imposes. This won't happen 99.9 percent of the time.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 1
    I understand and I'll fly with your truth, that **I don't need to know.**. But I've reached a spot where I need to. I have a `Generator` object that loops through all my `Items` objects, then calls `getName` from each `Item` to do something further. What is the issue with this? Then, in return, the `Generator` spits out formatted strings. This is within my framework, for which I then have an API on top of that people can use to run whatever the users provide but without touching the framework. – coolpasta May 05 '19 at 00:40
  • 5
    `What is the issue with this?` None that I can see. That's essentially what a `map` function does. But that's not the question you asked. You essentially asked "Are there *any* conditions under which a getter might be inadvisable." I replied with two, but that doesn't mean you abandon setters altogether. – Robert Harvey May 05 '19 at 00:45
  • As such, per your own review, do you believe that, assuming my getters are strictly only so that higher "ingester" objects can interview these `Items` to further provide data from the framework to the API, they are...valid? As in, this is a valid approach to retrieving data from objects? – coolpasta May 05 '19 at 00:45
  • 1
    You're asking the wrong guy that question. I'm a pragmatist; I do whatever best suits my specific programs, and don't put much stock in "principles" unless they serve my purposes. – Robert Harvey May 05 '19 at 00:46
  • I'm starting to go that path too. I've tested and tested, opened my API, fed it the worst, messed up internal objects, everything is predictable and at best, a module of my framework fails, in which case it tells the user, moves on and that's it. I really dislike dabbling in semantics, but I thought that there is a case that a lot of people are missing where it could turn into a monster problem later on. Something concise, which you provided, well, at least the second, because the first one is, to me, "let's do it because it's popular". – coolpasta May 05 '19 at 00:48
  • I don't try to predict the inherently unpredictable either. You can't account for every possible way someone might break your code; all you can do is test it the best you can and make it as robust as possible. – Robert Harvey May 05 '19 at 00:49
  • Let us [continue this discussion in chat](https://chat.stackexchange.com/rooms/93249/discussion-between-coolpasta-and-robert-harvey). – coolpasta May 05 '19 at 00:50
  • "The Hollywood Principle (aka Inversion of Control) states that you don't call into library code to get things done; rather, the framework calls your code." It would be absolutely, 100% completely insane if all libraries did that. It would be wholly inappropriate for a JSON parsing library to do that. It would be even more inappropriate for a list to do it. It is so widely *not* applicable that it makes no sense to even call this a principle. Most libraries *should* be called by the programmer's code, not the other way around. – jpmc26 May 05 '19 at 22:35
  • 3
    @jpmc26: *Frameworks.* Not libraries. – Robert Harvey May 06 '19 at 01:07
  • @RobertHarvey Where does this question limit the discussion to "frameworks," which you don't even define so that people can tell the difference? I probably know what you mean, but to someone who's sitting here thinking *getters* are bad, you can't assume they will. Not to mention you start off saying "libraries" and then quietly switch to "frameworks," making your answer read like there's no distinction. If your answer's reasoning is only applicable under certain conditions, it **should clarify what those conditions are and not make the reader guess at what you're talking about**. – jpmc26 May 06 '19 at 01:52
  • @RobertHarvey To clarify, what I understand you to mean by "framework" is some library that's designed to activate on specific events (like an incoming web request, a user keypress, or even a command line invocation). Even these might arguably violated the principle, though, since they typically require the program to invoke some kind of initialization code to get them started (either by actually performing the initial invocation that leads to your code or setting them up to reach their listening state). – jpmc26 May 06 '19 at 02:24
  • 1
    The Hollywood Principle is a rule for frameworks (as opposed to libraries). If you're writing a library as opposed to a framework, or if you're writing an application, why is it a concern? – user253751 May 06 '19 at 02:37
  • A framework is a worldview, a religion. A framework mediates a lot of things, to the point where object interactions do not happen without the framework doing the mediation. Frameworks tend to "hack" the language and the execution environment (reflection, annotation, dynamic code generation and injection, and sometimes byte-code generation). The result is an augmented, enhanced hybrid. A framework embraces some libraries but banishes others. Frameworks are application specific - http frameworks for http, audio frameworks for audio, etc. The Matrix (in the movie) is also a framework. – rwong May 06 '19 at 07:01
  • 2
    A framework is an attempt to turn a general purpose language into a domain specific one. A library is a collection of reusable algorithms. Either one will ask you to depend on it. It's up to you if you want to hard code either dependency or make the dependency easily substitutable. – candied_orange May 08 '19 at 02:37
2

Getters Leak Implementation Details and Break Abstraction

Consider public int millisecondsSince1970()

Your clients will think "oh, this is an int" and there will be a gazillion calls assuming that it is an int, doing integer math comparing dates, etc... When you realize that you need a long, there will be a lot of legacy code with problems. In a Java world, you'd add @deprecated to the API, everybody will ignore it, and you are stuck maintaining obsolescent, buggy code. :-( (I assume other languages are similar!)

In this particular case, I'm not sure what a better option might be, and the @candiedorange answer is completely on point, there are many times when you need getters, but this example illustrates the downsides. Every public getter tends to "lock" you into a certain implementation. Use them if you truly need to "cross boundaries", but use as little as possible and with care and forethought.

user949300
  • 8,679
  • 2
  • 26
  • 35
  • This is true, but what is the pattern solution to enforcing a data type / structure to an object's variable, as well to anything that uses the getter for that variable? – coolpasta May 05 '19 at 15:45
  • 2
    This example illustrates a different phenomenon, primitive obsession. Nowadays most libraries and frameworks have classes that represent `timepoint` and `timespan` respectively. (`epoch` is a particular value of `timepoint`.) On these classes, they provide getters such as `getInt` or `getLong` or `getDouble`. If classes that manipulate time are written so that they (1) delegate time-related arithmetics to these objects and methods, and (2) provide access to these time objects via getters, then the problem is solved, without needing to get rid of getters. – rwong May 05 '19 at 17:46
  • 1
    To summarize, getters are part of the API, and therefore getters need to be designed with sufficient considerations to all consequences, including past, current, and future ones. But that doesn't lead to a conclusion of avoiding or minimizing the number of getters. In fact, if there is a future need to access a particular piece of information for which a getter was omitted, it would have required a library-side API change and code change. Thus, the decision as to whether a particular getter should be implemented or avoided should be based on the domain and intent, not on preventive reason. – rwong May 05 '19 at 18:02
  • 1
    "millisecondsSince1970" stopped working for 32 bit ints before the end of January 1970, so I doubt there will be much code using it :-) – gnasher729 May 06 '19 at 15:31
  • 1
    @rwong Somewhat correct, but you assume that the preferred libraries that represent timepoints are stable. Which they aren't. E.g. [moment.js](https://github.com/you-dont-need/You-Dont-Need-Momentjs/blob/master/README.md) – user949300 May 06 '19 at 16:41
  • @gnasher729 Oops, but point is still valid. :-) – user949300 May 07 '19 at 20:28
  • Interestingly, NSDate/Date in objective c/Swift has methods “secondsSince1970” returning double, but internally stored seconds since a reference date that I believe is in 2001. As a result, secondsSince1970 is calculated, with rounding errors, and two very very slightly different dates could return the same value. – gnasher729 Sep 03 '22 at 20:13
1

I think the first key is to remember that absolutes are always wrong.

Consider an address book program, which simply stores people's contact information. But, let's do it right and break it up into controller/service/repository layers.

There's going to be a Person class that holds actual data: name, address, phone number, etc.. Address and Phone are classes, too, so we can use polymorphism later on to support non-US schemes. All three of those classes are Data Transfer Objects, so they're going to be nothing but getters and setters. And, that makes sense: they're not going to have any logic in them beyond overriding equals and getHashcode; asking them to give you a representation of a full person's information doesn't make sense: is it for HTML presentation, a custom GUI app, a console app, an HTTP endpoint, etc.?

That's where the controller, service, and repository come in. All of those classes are going to be logic-heavy and getter-light, thanks to dependency injection.

The controller is going to get a service injected, which will expose methods like get and save, both of which will take some reasonable arguments (eg., get might have overrides to search by name, search by zip code, or just get everything; save will probably take a single Person and save it). What getters would the controller have? Being able to get the concrete class's name might be useful for logging, but what state will it hold other than the state that's been injected into it?

Similarly, the service layer will get a repository injected, so it can actually get and persist Persons, and it might have some "business logic" (eg., maybe we're going to require that all Person objects have at least one address or phone number). But, again: what state does that object have other than what's injected? Again, none.

The repository layer is where things get a little more interesting: it's going to establish a connection to a storage location, eg. a file, an SQL database, or an in-memory store. It's tempting, here, to add a getter to get the filename or the SQL connection string, but that's state that's been injected into the class. Should the repository have a getter to tell whether or not it's successfully connected to its data store? Well, maybe, but what use is that information outside of the repository class itself? The repository class itself should be able to attempt to reconnect to its data store if need be, which suggests that the isConnected property is of dubious value already. Further, an isConnected property is probably going to be wrong just when it would most need to be right: checking isConnected before attempting to get/store data doesn't guarantee that the repository will still be connected when the "real" call is made, so it doesn't remove the need for exception handling somewhere (there are arguments for where that should go, beyond the scope of this question).

Consider also unit tests (you are writing unit tests, right?): the service isn't going to expect a specific class be injected, rather it's going to expect that a concrete implementation of an interface is injected. That lets the application as a whole change where the data is stored without having to do anything but swap out the repository being injected into the service. It also allows the service to be unit tested by injecting a mock repository. This means thinking in terms of interfaces rather than classes. Would the repository interface expose any getters? That is, is there any internal state that would be: common to all repositories, useful outside of the repository, and not injected into the repository? I'd be hard-pressed to think of any myself.

TL;DR

In conclusion: aside from the classes whose sole purpose is to carry data around, nothing else in the stack has a place to put a getter: state is either injected or irrelevant outside of the working class.

minnmass
  • 119
  • 2
  • I find this line of thinking very similar to the debate about the proper use of C# properties. In short, properties (which can be getter-only or getter-setter-pair) are expected to uphold a certain contract, such as "what you set is what you get", "getter should be mostly side-effect-free", "getter should avoid throwing errors", etc. Contrary to your conclusion, there are many object states that are beneficial to be exposed as properties (or getters). Examples are too numerous to quote here. – rwong May 05 '19 at 17:55
  • 2
    @rwong I would be interested to know some examples, let's say for a "normal" "enterprise" application implemented by one team. Let's also assume for simplicity's sake there are no third parties involved. You know, a self-contained system, like a Calendar, Pet shop, whatever you want. – Robert Bräutigam May 05 '19 at 18:49
1

What is one thing, without opinions, that will break if I use getters sensibly and for data that clearly the object's integrity doesn't break if it puts it out?

Mutable members.

If you have a collection of things in an object that you expose via a getter, you are potentially exposing yourself to bugs associated with the wrong things being added into the collection.

If you have a mutable object that you are exposing via a getter, you're potentially opening yourself up to the internal state of your object being changed in a way that you do not expect.

A really bad example would be an object that is counting from 1 to 100 exposing its Current value as a mutable reference. This allows a third party object to change the value of Current in such a way that the value could lie outside the expected bounds.

This is mostly an issue with exposing mutable objects. Exposing structs or immutable objects isn't a problem at all, since any changes to those will change a copy instead (usually either by an implicit copy in the case of a struct or by an explicit copy in the case of an immutable object).

To use a metaphor that might make it easier to see the difference.

A flower has a number of things which are unique about it. It has a Color and it has a number of petals (NumPetals). If I'm observing that flower, in the real world, I can clearly see its color. I can then make decisions based on that color. For example, if color is black, do not give to girlfriend but if color is red, give to girlfriend. In our object model, the color of the flower would be exposed as a getter on the flower object. My observation of that color is important for actions that I will perform, but it does not impact at all on the flower object. I should not be able to change the color of that flower. Equally, it does not make sense to hide the color property. A flower generally cannot prevent people from observing its color.

If I dye the flower, I should call the ReactToDye(Color dyeColor) method on the flower, which will change the flower according to its internal rules. I can then query the Color property again and react to any change in it after the ReactToDye method has been called. It would be wrong for me to directly modify the Color of the flower and if I could then the abstraction has broken down.

Sometimes (less frequently, but still often enough that it's worth mentioning) setters are quite valid O-O design. If I have a Customer object it is quite valid to expose a setter for their address. Whether you call that setAddress(string address) or ChangeMyAddressBecauseIMoved(string newAddress) or simply string Address { get; set; } is a matter of semantics. The internal state of that object needs to change and the appropriate way to do that is to set the internal state of that object. Even if I require a historical record of addresses that the Customer has lived in, I can use the setter to change my internal state appropriately to do so. In this case it does not make sense for the Customer to be immutable and there is no better way to change an address than to provide a setter to do so.

I am not sure who is suggesting that Getters and setters are bad or break object oriented paradigms, but if they do, they're probably doing so in response to a specific language feature of the language that they happen to use. I get the feeling that this grew out of the Java "everything is an object" culture (and possibly extended itself to some other languages). The .NET world doesn't have this discussion at all. Getters and Setters are a first class language feature that are used not only by people writing applications in the language, but also by the language API itself.

You should be careful when using getters. You do not want to expose the wrong pieces of data and nor do you want to expose data in the wrong way (i.e. mutable objects, references to structs that can allow a consumer to modify internal state). But you do want to model your objects so that the data is encapsulated in such a way that your objects can be used by external consumers and protected from misuse by those same consumers. Often that will require getters.

To summarize: Getters and Setters are valid object-oriented design tools. They're not to be used so liberally that every implementation detail is exposed but nor do you want to use them so sparingly that an object's consumers cannot use the object efficiently.

Stephen
  • 8,800
  • 3
  • 30
  • 43
-1

What is one thing, without opinions, that will break if I use getters...

Maintainability will break.

Any time (I should say almost always) you offer a getter you basically give away more than was asked of you. This also means that you have to maintain more than was asked, ergo you lower the maintainability for no real reason.

Let's go with @candied_orange's Collection example. Contrary to what he writes a Collection shouldn't have a getter. Collections exist for very specific reasons, most notably to iterate over all items. This functionality is not a surprise to anyone, so it should be implemented in the Collection, instead of pushing this obvious use-case on the user, using for-cycles and getters or whatnot.

To be fair, some boundaries do need getters. This happens if you really don't know what they will be used for. For example you can programmatically get the stacktrace from Java's Exception class nowadays, because people wanted to use it for all sorts of curious reasons the writers didn't or couldn't envision.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • 1
    Counterexample. Consider a function that needs to iterate two `Collection`s simultaneously, as in `(A1, B1), (A2, B2), ...`. This requires an implementation of "zip". How do you implement "zip" if the iterate-over function is implemented on one of the two `Collection` - how do you access the corresponding item in the other? – rwong May 05 '19 at 18:21
  • @rwong The `Collection` has to implement `zip` with itself, depending how the library is written. If it doesn't, you implement it and submit a pull request to the library creator. Note that I agreed that some boundaries do need getters sometimes, my real problem is getters are everywhere nowadays. – Robert Bräutigam May 05 '19 at 18:28
  • In that case, that implies that the library has to have getters on the `Collection` object, at least for its own implementation of `zip`. (That is, the getter has to have package visibility at least.) And now you depend on the library creator accepting your pull request ... – rwong May 05 '19 at 18:49
  • I don't quite understand you. The `Collection` obviously can access its own internal state, or to be more precise any `Collection` instance can access any other's internal state. It's the same type, no getters needed. – Robert Bräutigam May 05 '19 at 18:53
  • I hear you too, but then again, what exactly is the better solution to getters? I know it's out of the scope of the question. – coolpasta May 05 '19 at 19:25
  • @coolpasta Better solution is: Implementing the behavior where the data resides instead of pushing it out to the user. This requires of course a different design, you can't just take DTOs and start adding methods. So yes, that is a bit out of scope. – Robert Bräutigam May 05 '19 at 19:54
  • @RobertBräutigam I see, any chance to send me to a concrete implementation of the said pattern? Please. I promise this helps tremendously. – coolpasta May 05 '19 at 20:07
  • You know, aside from *every* container having to implement zip, how would you zip disparate containers? Or get the cartesian product? Or anything else more involved? – Deduplicator May 05 '19 at 20:17
  • @coolpasta For example https://github.com/vanillasource/gerec. It's a library for RESTful HTTP Clients, it has I think maybe 1-2 getters (where the use-case is entirely up to the client), even though it's a "data" communication library. – Robert Bräutigam May 05 '19 at 20:44
  • @RobertBräutigam Sounds like it has a robust architecture that I could learn a lot from, but...what exactly are they doing? How exactly do they serve data and so on? – coolpasta May 05 '19 at 22:00
  • Sorry, but no. Programs aren't unmaintainable because `List` has an index base access method. On the contrary, it makes writing a method to get all combinations of 2 elements trivial. **Too many or bad boundaries** will do a lot more to hurt maintainability than some accessors will. – jpmc26 May 05 '19 at 22:41