A lot of people have mentioned encapsulating the specifics of the implementation, which to me is the biggest reason to use getters and setters in a class. With this, you also get a lot of other benefits, including the ability to throw out and replace the implementation on a whim without needing to touch every piece of code that uses your class. In a small project, that's not a big benefit, but if your code ends up as a well-used (internal or public) library, it can be a huge benefit.
One specific example: complex numbers in mathematics. Some languages have them as a language or framework feature, others don't. I will use a mutable class as an example here, but it could just as easily be immutable.
A complex number can be written on the form a + bi
with real and imaginary parts, lending itself well to [gs]etRealPart
and [gs]etImaginaryPart
.
However, in some cases it's easier to reason about complex numbers on polar form re^(iθ)
, giving [gs]etRadius
(r) and [gs]etAngle
(θ).
You can also expose methods like [gs]etComplexNumber(realPart, imaginaryPart)
and [gs]etComplexNumber(radius, angle)
. Depending on the argument types these may or may not need different names, but then the class' consumer can use either as fits its needs.
The two forms are interchangeable; you can fairly easily convert from one to the other, so which form the class uses for internal storage is irrelevant to consumers of that class. However, consumers may use either form. If you choose the form a+bi for internal representation, and expose that using fields rather than getters and setters, not only do you force the class consumers to use that form, you also cannot later easily change your mind and replace the internal representation with re^(iθ) because that turns out to be easier to implement in your particular scenario. You're stuck with the public API you have defined, which mandates that specifically the real and imaginary parts are exposed using specific field names.