In this age of moral programming, most "good programming guides" stress the vital importance of implementing getThis and setThat methods for every publically-accessible attribute. Supposedly, this way hides the internal structure of the class. Information hiding at its best.
For example, the class Interval might have attributes start and length, so we'd have to write
class Interval {
double start;
double length;
public:
double get_start() { return start; }
double get_length() { return length; }
double set_start(double s) { start = s; }
double set_length(double l) { length = l; }
};
In a stroke, public data members are gone. You are only allowed to have public member functions.
In my running example, supposedly we should hide from the poor programmer that intervals have a start and a length. Obviously, though, any programmer stupid enough not to notice that the above Interval has-a start and a length should probably be assigned as far away from real code as possible, where they cannot do much damage. Perhaps a career in upper management is indicated?
Computing is about data. Programming languages already have ways to read and write data. To read variable x, we usually say "x", not "get_x()". To write variable y, we usually say "y = ...", not "set_y(...)". Except in COBOL, obviously.
People who plan hierarchies full of accessors always seem to me as if they are about to plan a lengthy campaign in Russia with heavy equipment. The end-result is always the same: heavy code, tanks getBoggedDown in autumn, deadlines missed due to unreadable code, resupply impossible when said tanks getFrozenInTheirTracks inWinter.when(), and an end-result of isTotalFailure().
I would implement Interval differently:
struct Interval {
double start;
double length;
};
To read the length of interval i, say "i.length". To write the start of interval i, say "i.start = 1.7". The only disadvantage of this elaborate scheme is that suddenly high-level object oriented programming becomes embarrassingly easy. Even an upper-level manager could read code that uses the language, instead of constructing elaborate accessor mechanisms.
Suddenly, an Interval ends at "i.start+i.length", not at "i.getStart()+i.getEnd()". It's all so simple -- and simplicity is the programmer's best -- and only -- friend.
But what if the internal representation if changes?
With accessors, you just need to change their implementation. Of course, suddenly the class interface sprouts new methods to get and set the new data members, but if we ignore that fact then the change really is transparent.
For instance, say we decide to implement Interval as a <start,end> pair rather than as a <start,length> pair. In fact, there can never be a legitimate reason to make such a change, but I shall humour the poor misguided souls who confuse this fake agility of changing unimportant details with the true agility of being able to write and read code.
With accessors, we now have
class Interval {
double start, end;
public: // get_start and set_start remain unchanged
double get_length() { return get_end()-get_start(); }
double set_length(double l} { set_end(get_start()+l); }
// get_end() and set_end(double) omitted
};
Fairly easy stuff, especially given you're already used to all those horrible nested braces since you have to use them in application code all the time.
On the other hand, your upper management-level twit is convinced you cannot change the easy version above. Some disabusement is in order, then...
struct Interval {
private:
class LengthHelper {
double& start;
double& end;
LengthHelper(double& s&, double& e) : start(s), end(e) {}
friend class Interval;
public:
operator double() { return end-start; }
const LengthHelper& operator=(double l) { // see C++: (a=b)=c;
end = start+l;
return *this;
}
}
public:
double start;
double end;
LengthHelper length;
Interval() : length(start, end) {}
};
LengthHelper explains how to read and write the length of an interval. And it exactly mimics the get_length() and set_length() methods of the accessorized version. Hide it away in some infrastructure library; use some standard template structure to hide trivial calculations from you.
The user can continue to write code that accesses data as though it were data, and doesn't pretend it's doing something hard with all those function calls.
The difference is clear: In the accessorized versions, the creator of the infrastructure tell the application programmer "you need to clutter up every use of my class with get_this() and set_that() methods, because I can't be bothered to do my job". The sane version, above, tells the application programmer "let's start off with a struct; if we need to change something, we can do it, and the complexity stays hidden in the library".
Another claim made for accessors is that they allow the programmer to add side effects to the method. Presumably, this is particularly important for the set_that().
But note that the above logic is usable for running arbitrary code during both get_this() and set_that() methods. This code could easily include side effects. The only difference between writing your accessors and allowing the compiler to insert them using a *Helper class is in their syntax.
Why accessors?
Accessors offer nothing. Simple libraries become complex, complex libraries become nightmares. The ability to change interface is paid-for by all users of the library class.
The public interface of a class should be determined by what its users need. Not by what some arbitrary style guide for COBOL decided was simplest. Just as we allow public member functions -- and indeed, we must allow them -- so should we allow public data members.
Java doesn't really let you do stuff like the above example. It is "the COBOL of the 90s". The year is 2005 -- Let's write libraries that let us program in the language. Let's stop using COBOL. Let's demand what we need from the toolchain vendors, from our management, and from their consultants.
Let's keep the simple things simple, so we can do what we need to do: simplify the complex things enough to solve them.