No Getters
That’s the normal form of this technical practice.
No Getters
There’s some uncertainty around what “No Getters” means. OK, there are two versions of it.
Yegor Bugayenko in Elegant Objects Vol. 1 says,
It’s all about prefixes
That’s section 3.5.3 in Elegant Objects Vol. 1. I very strongly disagree with it.
When it’s all about the prefix, then you’re not effectively encapsulating behavior. If you’re not a compulsive reader of my blog, and why would you be – I’m me. Then you may not know my view of encapsulation: Behavior. Encapsulation is all about behavior. It’s what allows objects to message each other, by asking them to do something.
An example given for ‘just drop the prefix’ is the difference between
class Cash{
private final int value;
public int getDollars(){ return value; }
}
getDollars
is saying, “Go into your data, find dollars, and return it.”
I don’t disagree with this sentiment. I think it’s super explicit about what the object is doing. There’s no data hiding or implementation hiding.
Yegor follows with
class Cash{
private final int value;
public int dollars(){ return value; }
}
dollars
is asking, “How many dollars do you have?”
Again – I don’t disagree with this view. Which follows A LOT of Object Oriented literature about respecting the object. This is BETTER, but it’s still wrong. This doesn’t help us write good object-oriented code. My love for Elegant Objects Vol. 1 (it’s a fantastic book) is due to it helping me finally click on what OOP is. It pains me to see these things that get SO CLOSE… and stop at a point I’d consider a failure to reach better OOP.
Fred George has produced nightmares of a single question when trying to get information from an object
Why?
Why do you want the data?
What are you going to do with it?
From these questions, you’ll find THE BEHAVIOR that exposing the naked data is enabling.
I also disagree with Yegor that dropping the get
makes the data non-naked. It’s still raw, naked, behaviorless data. It’s wrong.
Once you identify the behavior, you’ve identified something that elsewhere in the code could use. It’s not available to them. It’s in that one bit of code in that one spot for that one thing. It’s not reusable.
There’s behavior around the dollars
data. This behavior needs to be encapsulated. A fully encapsulated class is one that only exposes behavior. That only provides interaction. If your object returns raw data – your object is not encapsulated.
I don’t know if Fred started the thought about never returning your data, or if it evolved from my evolved definition of encapsulation being based on behavior. I’ll give Fred a lot of the credit though. While working with him, I never returned a reference I held.
Going back to the questions that have now identified the behavior you want – encapsulate that. Put that behavior into the class. Yegor’s Cash
class is still a databag; he just names it in a way it doesn’t shout it from the rooftop. The databag-ness is hidden from us. We’re hiding the smell.
Concepts – Not data storage
One of the points I’ll get to in another post is about not passing around data storage types or primitives. Yegor knows this, and his Cactoos library provides ways to avoid doing so. His Elegant Objects Vol. 1 examples do not.
I’ll write more on this in the future, as it’s one of the critical Technical Practices of maintainable code – for now, I’ll summarize.
By passing back an int
we’re passing back … an unknown. We’re providing the underlying storage mechanism.
Ignoring that we can do anything with this allowed by the operating system; what is it? Probably dollars… I hope some dollars…
It drives procedural code – Not object-oriented code. The exact thing he and I are both striving to get out of Object Oriented Code.
int amountToPay = costOfMeal().dollars();
int onHand = cash.dollars()
if(amountToPay <= onHand){
... do something ...
}
I’m forced to do behavior around the data, maybe with the data.
int amountToPay = costOfMeal().dollars();
int onHand = cash.dollars()
if(amountToPay <= onHand){
return new Cash(onHand - amountToPay);
}
This is so very wrong. Is this the only place in the whole code base that wants to know if something can be paid for; and pay for it?
What are we gonna do, turn it into a static method?!?!?! … oh, BTW – No Statics; another planned post.
No.
Let’s look at this example and our earlier questions.
What do you want the data for?
To compare to another value.
Cool… Ask the object to do it.
Cash amountToPay = costOfMeal();
if(cash.canCover(amountToPay)){
return new Cash(cash.dollars() - amountToPay.dollars());
}
Again
What do you want the data for?
To subtract an amount
Cool… Ask the object to do it.
Cash amountToPay = costOfMeal();
if(cash.canCover(amountToPay)){
return cash.subtract(amountToPay);
}
We’ve now taken TWO BEHAVIORS and put them into the object. Its data now doesn’t matter, at all. We are asking it to DO for us. Not politely asking it for its data. The data returned was still raw.
In the fully encapsulated example, we don’t know anything about how Cash
has it’s information. They types, the conversions – We know NOTHING.
We know NOTHING!
Do you know how beautiful it is when working with code to be so freed to know nothing about what you’re working with?
I get to ask one Cash
object if it canCover
another Cash
object. I can then get the difference of the two Cash
objects. Is that a new in, or float, or how does it want them rounded? What level of precision should that be?
We don’t have to know!!
I want to really hammer home that when you don’t return a reference you hold; your code is almost forced to return objects. These objects have behavior. These behaviors are how all objects interact… You’re now doing object-oriented programming.
Never Return A Reference You Hold
This is the one practice I advise engineers to adopt to improve their code base. It starts to force behavior into a single location. Any behavior around a piece of data is co-located with that data.
This isn’t a perfect solution; there’s a lot more to writing great OO code – and I’ve written my thoughts on most of them.
When you have getters, you CAN’T have object-oriented code. You can do OO without most of the other practices (though it’s still terrible), but when you have getters – you have databags and not objects – You don’t have Object Oriented Code.