NOTE: This document may be circulated or quoted from freely, as long as the copyright credit is included.
OOP experts condemn the strange but surprisingly common practice of providing write accessors (also called "setters") for practically every private instance member of a class. We use the term quasi class to describe this way of seriously undermining the object-oriented paradigm.
Recently we've been seeing a growing number of examples that combine that awful practice with the choice of a mixed unit representation for a numeric quantity. Examples of mixed units include:
| Numeric class |
a simple unit representation |
a mixed unit representation |
| weight | grams | pounds, ounces |
| angle | radians | degrees, minutes, seconds |
| distance | meters | miles, feet, inches |
| duration | seconds | days, hours, minutes, seconds |
|
Although the simple unit representations are usually more appropriate for the
hidden member data item, that's not our issue here. Whichever choice we
make for the private data, we often wish to provide accessor funtions so that
users of the class can easily obtain the representations that are most familiar
to them and to the ultimate end users. It's perfectly reasonable, therefore, to provide in a
C++1 |
Dynes, anyone? (January clarification)A couple of readers pointed out that weight strictly speaking is a measure of force not mass. For some scientific applications, therefore, it would be inappropriate to specify weight in units of grams, and a mass class would be required. However for applications that are subject to the earth's surface gravity, such as shipping packages or weighing medical patients, it's customary to treat them interchangeably. Scales that observe metric standards customarily specify units of grams or kilograms, so we'll stay with the example. |
Some programmers who don't appreciate the object-oriented paradigm then almost
automatically add these public
functions: void setPounds(const long x);
void setOunces(const short x);
void setGrams (const double x);
|
Such write accessors are questionable in any case, but they're even more indefensible with the mixed units. In what situations would a program want to change one part of the numeric representation without affecting the other? Suppose the program wants to set the weight of
a package to three and a half pounds. It might execute these
statements: | They can't explain itWhen you ask the programmer why he has provided those write accessors and how they support object-oriented concepts, he (they're usually male) is likely to reply that it's a "standard idiom" in C++ or Java. If you follow up by asking where he learned that standard idiom, he may cite a course or a textbook, or just examples that he saw in other programs. He may impatiently assure you that "everyone knows this", but he won't be able to provide a convincing explanation. |
If the program gets interrupted just after executing the first statement,
the pkgwt object will be in an inconsistent state that
corresponds to nothing in the application domain. In many applications, that would be
corrected as soon as the task resumes before anyone uses
pkgwt, but there may well be subtle
situations in which we don't dare leave an object in an inconsistent state even
for a few nanoseconds. If the Weight class
provides a sensible constructor and assignment operator, the program would accomplish
the desired result by coding simply pkgwt = Weight(3,8);
It's then up to the implementer of the assignment operator to assure that the operation
is atomic, if that's necessary (trivial with the pure-unit internal representation).
Calendar manipulations always invite "special" needs. For example, some event is always scheduled on the 7th of every month, so to get from the date of one to the date of the next the program wants to add one to the month without changing the rest of the date (except to advance the year when the month goes from 12 to 1).
In my programming work, assuming a decent Date
class, I've found using constructors and operators natural, easy to understand, and efficient,
and I've had no need to set components of a date individually. For example, using IDI's
Date class and
Calendar pseudo-class you can just increment
the date by the number of days in the current
month: d += Calendar::DAYS_IN_MONTH[d.month()] // C++
+ (d.month()==2 && Calendar::isLeapYear(d.year()));
d.addSet(Calendar.DAYS_IN_MONTH[d.month()] // Java
+ (d.month()==2 && Calendar.isLeapYear(d.year()))? 1:0);
If you need to do that often or if you find it hard to read, make it a function.
In object-oriented programming it is usually considered poor practice:
To do both in the same class is inexcusable.3
getPounds(), etc.cweisert@acm.org), and I'll change this article
accordingly.
Return to IDI Home Page
Return to Technical articles
Last modified 18 December 2010