OOP and the Get/Set issue
Posted: Fri, 2 December 2005 | permalink | No comments
Steinar, your comments on the limited utility of Get/Set methods both struck a chord and touched a nerve with me.
On the one hand, I completely agree that people need to spend more time writing methods that do something useful to an object rather than just exposing public methods. For example, how often have you seen something like this (in faintly python-inspired syntax):
obj.count += 1 [...] obj.count -= 1 [...] if obj.count == 0: obj.destroy
Instead of the far more descriptive (and well-encapsulated):
obj.register_user() [...] obj.unregister_user() [...] obj.destroy_if_unused()
(or any one of a thousand other similar idioms). This is a contrived example, of course, and a real example would involve doing at least a dozen operations on an object's public attributes from another class (which results in class-associated logic being located in other classes in the system) instead of putting it in a method and calling that (thus keeping all of the logic with the data it's manipulating -- which is the point of OOP).
Anyone who writes code like the first example above and calls it OOP is kidding themselves. Of course, you could make the code even more ridiculous with getter/setters:
obj.setCount(obj.getCount()+1) [...] obj.setCount(obj.getCount()-1) [...] if obj.GetCount() == 0: obj.destroy
Incidentally, if you ever see anyone turn the first example into the last example and say they're "improving encapsulation", please do the programming world a favour and kill them. kthx.
On the other hand, Steinar, you say "I'm not denying that getters and setters for a few variables might make sense", which clouds the issue immensely. The idea of routing attribute access through a method is immensely useful. It even has it's own name -- the Uniform Access Principle. A few examples are in order (you might already have thought of these, Steinar, but who knows who reads my dribblings!):
- (Not taken from real life, but certainly plausible) An accounting system has an Invoice class, which contains line items. After an all-nighter binge-drinking Stupid, the original programmer wrote the Invoice class to store the total as a separate attribute. Then, after sobering up (and possibly some cluebat-inspired rehab) s/h/it decides to create the total by adding up the subtotals in the line items when someone wants the invoice total. Except, by now (it was a long rehab session), there's about 15,000 places which ask for inv.total, and finding them all and replacing them with inv.total() is Aegean-stables level pain.
- ("Based on a true story") A class that interfaces to some piece of physical hardware has an attribute whose value gets sent to the hardware. Either a hardware change or a "we didn't think of that" moment results in a requirement that the value of the attribute, as sent to the hardware, be within certain bounds. Just truncating to the required size isn't going to work. What you'd like to do is raise an exception (or log a call trace) when that attribute is set with and out-of-bounds value, so you can track who is setting the OOB and fix the problem at the source. But of course, since it's just an attribute, you can't do a bounds check and exception-throw, so it's call-wrapping-and-funky-logic time.
- (Taken from my last few days programming, but it happens often enough) A class hierarchy has a map of IDs and names stored in it (these are dynamic maps, but are set via config file so don't change over time or during program invocation). This is stored in an object attribute. Yay for object attributes. Then, somebody decides that a new class (in the same hierarchy) should be able to dynamically generate value maps in some circumstances (specifically, from a remote database). Since these maps can change in real-time, we can't just generate-and-cache.
In my real-life case, there were few enough places to change all of the calls to a method, and since it's all written in Python I could have played __getattr__ games to get back to a method if I'd had to, but it would have been messy.
But imagine a system written in a language that doesn't (somehow) support the Uniform Access Principle, and which was widely deployed (possibly even used by programs outside of our direct control) so the users of the class couldn't be adapted easily. Nightmare.
Now, the real solution to the problem is to only use languages which support the UAP (Python can be hacked to do it; Ruby is, IMHO, almost a case-study in How To Do It; and there are other good languages out there). But, in ass-backwards languages that can't transparently map attribute accesses to method calls, I'd rather deal with getX()/setX() everywhere than have to try and perform the necessary gymnastics to later support dynamic value generation from what-was-previously-an-attribute.
If, on the other hand, your post was primarily targeted at said ass-backwards languages, or programmers who cargo-cult manual getter/setters everywhere, let me join you in your derision. <grin>
Post a comment
All comments are held for moderation; markdown formatting accepted.