No Getters & Setters
Last updated
Last updated
You know how we talked at length about encapsulation, and how an object must maintain its integrity and protect its internals ?
Nothing illustrates this problem more than getters and setters.
We've been taught to protect our private data from the very first days of school. Every programming course teaches you about how variables should be private.
And how to create a setter to expose them...
What's the point, then ? Do we really need dozen more keywords when we can simply use the public modifier ?
Us, lazy developers, totally accept writing more code just for the sake of following a principle blindly ?
Something is wrong, I can feel it...
Getters expose our internals. That's it.
It's taking the object by the collar, emptying their pocket and walking home like nothing happened.
It's illegal, it's a federal crime.
At least, we give it a chance to react. Since a getter is a method, we can intercept the call. I don't know how this can be useful, I haven't done anything out of it for the past decade.
But we're still exposing our internal. We're telling the world that it's fine to know this detail, it's part of the public contract.
And once it's part of the public contract, you owe to respect it for the rest of your life or pay the consequences.
We must protect the others from things they shouldn't know, because what they know can be held against them.
Ask yourself : why do you want to expose some information ?
A few reasons come to mind :
To perform an action using that information
To create objects using that information
To expose that information to the outside world
Let's tackle them one by one.
Recall the magic number game. That's how we could have naively implemented the MagicNumber class.
What's wrong with this code ? It's disrespecting the Guess object.
Instead of treating it properly, it extracts its heart from its chest, inspect it, and doesn't even give it back. It's thievery.
Guess has no chance of knowing what's happening to it, because it is left out of the process.
Why do we need the value of Guess ? To compare it with our own value. Isn't Guess a grownup perfectly capable of telling us if the value we give it is greater or lower ?
I think Guess would like to have its word. It's smart enough to carry that task.
This code is pure. It's composed of one-line methods, perfectly understandable by anyone. The concept it represents (a user guess) and how it will be used is clear. You can put this object on any repository and have it ready for use as is. It's perfectly extensible and can take input from any source. It's perfect.
Why is it good Object-Oriented code ? Because all the semantic of our objects are kept inside the objects with the knowledge. Guess knows how to compare its value to another value, and in fact nobody can ever be better at it than themselves.
The trick to prevent exposing a value is to ask yourself why you need that value in the first place, and how can the object owning that value can provide a service preventing others from knowing it.
And MagicNumber can happily revert to its proper OO state.
MagicNumber talks with Guess, they exchange in a collaborative manner. They're partners in crime. MagicNumber trusts Guess with all its guts, so much that it happily exposes its own internal value to Guess.
However, as far as Guess is concerned, it's just a number. Guess doesn't even know MagicNumber exists. They can accept any number as long as it's a number.
You often have to "revert your thinking" to find the proper OO abstraction.
Sometimes, you need to use an object to create another object.
Here's a simple class representing a duration, a case of Wrap Primitives.
It's a simple class. You can have many constructors to simplify the creation of Duration objects from many different inputs, a case of Static Constructors.
Now, let's say you're developing a driving school app in which students can reserve lessons, and the price of the lesson depends how long the lesson is. For simplicity, we'll say the rate is 2 credits per hour of lesson.
We can create a Compute Object as follows.
Now, how can we connect these two objects ? It'd be nice to have some
See, using a getter almost always lead us to poor design. Here, we're exposing an implementation detail : how to turn seconds into hours.
Even more evil, we're exposing the fact that CreditsToPay needs a primitive representing hours. That's way, too much information.
What can we do instead ? Update our Duration classes to convert into hours and keep that logic inside the Duration class.
And then
That's much better. But we still have the problem of exposing the fact that CreditsToPay exposes its dependency toward a specific integer format : hours.
How about we let CreditsToPay take a Duration and deal with it by itself ?
And simply inject it.
There we are. Our encapsulation is perfect. The algorithm of computing the price in credits can change in only one single focused place, and can be a function of minutes or seconds if necessary. All it takes is to open the proper class, change one line of code, and voilà !
Hence, when you need to create new objects, try as hard as possible to pass the object itself, and try to design your objects to carry meaningful values. They will be much more flexible and useful.
In this case, we need a mechanism to create a representation of the object. One that doesn't violate our object thinking and, most importantly, encapsulation.
Unfortunately, there's no purely object-oriented way to do so, or at least not pragmatically. Our last resort is to Expose Snapshots.
What's wrong with setters ?
Everything.
They're not behavioral. They're like kids hiding their hands behind their back. They're mischievous. They hide the context and the true meaning of the operation.
Let's take back our trusty Duration class.
Every time setSeconds is called, a Duration object gets treated improperly. Some other object coming from nowhere just grab this guy, stuff some integer in their pocket and just goes away without saying a word. Why would you do that ?
Even worse : in this context, we'll likely find a getter. After all, if you put cash on your wallet, you probably intend to take that cash back later on.
One day, our Duration class decides that it's enough, and asks whoever uses his setSeconds method to tell it the truth. But the truth hurts, it's too painful for them.
Good heavens. That's what a horror show would look like.
Here, the calling object exposes the hidden gem inside Duration, changes it and put it back. Everything is wrong in this transaction !
First, let's talk about the operation itself. Isn't Duration capable of doing this simple operation ? Do we have to treat it like an idiot ? In short, no, we don't !
Duration is perfectly capable of adding a value to itself.
That's better.
Do you like it ?
Yes ?
It's awful.
Why do we suddenly need to add a number to a Duration ? It doesn't make sense to escape the reality of Duration and pass it a number. As far as we know, Duration may decide to represent that value as anything it wants, so why do we rely on a primitive again ?
It's common in functional programming to talk about closures, but not the scoping definition. I'm talking about the mathematical definition : operations are closed against the same type of values.
In other words, our Duration class can be a mathematical component with operations such as addition and substractions, but should do so using other Durations.
Ah, isn't it better now !
Do you like it now ?
Yes !
It's still awful.
In what world would 1 + 2 change the meaning of 1 ? Not in mine, at least !
Duration has all the characteristics of Immutability : it caries a value with an intrinsic meaning.
Doesn't it make sense ? Two objects carrying the same value are logically equivalent. You can have two Duration of 1 second and treat them exactly as if they were the same objects.
That's the property of a Value Object.
Very mathematical, isn't it ? This way of programming stems from functional programming. Indeed, proper OO has a lot of common with FP.
So, it's only natural that instead of changing the value of our Duration, we emit a new one.
Is it better now ?
Yes ?
Are you sure about it ?
Well, of course it is ! This screams OO the proper way. By abandoning the setter, we reflected on how our class had some very interesting mathematical properties, how to leverage them to enhance our code and to give back responsibility to our Duration.
Getting rid of setters takes us a long way, uh ?