Getter/Setter insulation is the Java Tutorial BS - in past 10 years I've never heard of a case where they would save any meaningful time. I have however seen several enterprise grade projects with 100kLOC+ auto-generated code with nothing but getters and setters - whole libraries of nothing but classes holding variables - and unit tests testing them.
As a matter of fact, I've never encountered a getter/setter case that was not imposed by some ORM database mapping.
If G/S exists for sake of insulation the interface is broken already, and refactoring will need to be done. There is never, in real code, a feature request or design change that G/S would absorb.
G/S came to be after Java did away with what Delphi already did really well - properties. Delphi also exposed why "smart" G/S concept is bad. Entire VCL was built on this idiom:
void Widget::setSize(int newSize) { if (size != newSize) { size = newSize; doSomethingFancy(size); }}
This lead to endless bugs where a new object was created but the setSize was called with same value as deep-hidden default, causing a cascading - nothing. Entire event chain failed, and application was broken, yet running perfectly.
It's about design: Don't ask - tell
Procedural OO (CRUD ORM, get/set based apps) results in this:
getFoo().getBaz().getBar().x += 17;
Better:
XAffector xa(17);BarPolicy ba(xa);foo.apply(baz, ba);
Even if in this case it seems that latter case is coupled closer and more confusing, practice shows it's not. There is only one level of coupling (xa+ba and foo+baz+ba).
And once you have that, you might as well move everything into implementation detail:
Foo::apply(Baz baz, int x) { XAffector xa(17); BarPolicy ba(xa); // directly apply ba to baz};
Getters and setters are hollow and don't express anything. They are just accessors.
OO is about expressing function, role and intent. G/S don't do anything about it.
---
The above applies to exposing variables or single values via G/S. Compare this to vector:
int vector::getSize(); // gettervoid resize(int newSize); // not setter -+void clear(); // not setter -+-- function/role/intent (or whatchawannacallititerator end(); // not setter -+
All of these are interconnected in non-trivial way, but they are all about encapsulating some complex internal state.
With G/S pairs, this logic is moved elsewhere. Consider:
void myResize() { int oldSize = vec.getSize(); int oldCap = vec.getCapacity(); if (oldSize+1 >= oldCap) { vec.setSize(oldSize*2); vec.setDataPointer(allocateHelper(oldSize*2); vec.setCapacity(oldSize*2); }};
Once this happens, changing G/S becomes impossible. It might make sense to change setSize() and setCapacity() so that they allocate new memory itself - guess what happens with above code. Now imagine that such setters are referenced thousands of times.
Interfaces are incredibly expensive to change. So keep them minimal and expressive about domain they encapsulate - not the variables.
"Getters" and "Setters" must encapsulate the invariants of the problem domain they are implementing. That is a very different thing from getFoo()/setFoo() - in languages with properties that is a problem solved by compiler.
PS: rantish....