Is there a name for this 'Aha' experience?
I was trying to explain to a new C++ programmer that having public "get" and "set" members is not encapsulation; that it's no better than having public data members; and that it isn't OO.
I'm not good at explaining things, so my mind was filled with concepts like "code to the interface", "double-dispatch", "abstract base classes", etc., but I couldn't come up with what I wanted to say.
Anyway, my question is this. You know that massive dividing line that separates C++ programmers (I only speak C++, so that's all I can talk about) into two camps? The one camp, where everything is still procedural, getters and setters abound, and nothing is really OO? And the other camp, that had the epiphany one day, and finally "got" OO. (I say finally because it took me several years of C++ coding before I got it.) Is there a word or phrase for that epiphany?
I ask, because it would be handy to have an agreed-upon term for that, so that I could google for it, and come up with some websites that explain exactly why doing things in an OO way is better than public getters and setters.
Grumpy Old-Timer
Tuesday, February 24, 2004
'I was trying to explain to a new C++ programmer that having public "get" and "set" members is not encapsulation; that it's no better than having public data members; and that it isn't OO.'
I guess I'm as naive as the guy you were talking to, because while I can see how you _could_ write get and set members that were no better than having public data members, I don't see why that means that _any_ get and set routines, no matter how they're written, are no better than having public data members. Perhaps for the benefit of people like me you could elaborate.
Kyralessa
Tuesday, February 24, 2004
>I don't see why that means that _any_ get and set routines, no matter how they're written, are no better than having public data members. <
Agreed. If I have an 'Employee' class for some lame-o HR app I'm working on, and I want to keep track of the employee's birthdate, wouldn't it make sense to have a 'setBirthDate()' method that wouldn't allow me to set the birthdate to February 29th in some non-leap year?
Diogenes
Tuesday, February 24, 2004
Wouldn't it be better (or at least equivalent) to have a public "Date birthDate" data member, where the "Date" class itself doesn't support an invalid date?
The 'problem' is that sometimes a class logically deserves to have some private data members (e.g. the "state" associated with a TCP stack) ... and writing get/set accessors to every data member is just a round-about way of violating this privacy. If it's truly just a collection of independent data members (any of which can be get/set from elsewhere) then perhaps it should be a struct instead of a class.
Christopher Wells
Tuesday, February 24, 2004
What if the employee's computed age has to be between say, 18 and 65? What's the better approach - putting this logic in a 'setBirthDate' method or creating some new 'Date' subclass that contains this logic?
Diogenes
Tuesday, February 24, 2004
I'd guess it would be worth the overhead of creating a new class named EmployeeBirthDate derived from Date, instead of puting the logic in Employee.setBirthDate, if and only if you you'd want to reuse EmployeeBirthDate functionality in more classes than only the Employee class ...
... or, if there's more than one way to set Employee.birthDate. For example, as well as Employee.setBirthDate there might be a Employee.constructSelfFromXml method (whose implementation doesn't use the Employee.setBirthDate method), in which case using a EmployeeBirthDate class guarantees that constructSelfFromXml as well as setBirthDate enforces this functionality.
Christopher Wells
Tuesday, February 24, 2004
While you guys were arguing about the best way to architect the date class, I finished the app. :}
Dan Brown
Tuesday, February 24, 2004
Probably the most O-O way to associate the birthdate with the employee is to pass it in though the constructor.
I mostly agree with avoiding getters and setters, preferring to create objects in a valid state and then let the objects themselves manage their transitions through other valid states using logical transformations defined internally to the object.
The problem comes, though, when objects get to be a little bit too smart, often because of I/O considerations. They have to know how to display themselves ten different ways, they have to know how to publish themselves as XML or push themselves into a database, and on and on. This is the kind of stuff that always breaks things down, and I've yet to see a good all-purpose solution to the problem. I keep hoping, though :)
Another place where I see trouble is when objects need to collaborate in a way where the end result is dependent on interactions between the two objects. For example, in computing a sum of two numbers in an OO way, the usual approach is to define a method on one number that computes its sum with the other number. To do this correctly, the first one has to somehow observe the state of the second one. This often involves double dispatching and defining "helper methods" which insulate each player from having to expose its internals directly, all of which complicate things a lot. Most people confronted with all of this resign themselves to getters and setters and just move on.
just an observer
Tuesday, February 24, 2004
Always having public get and set methods is equivalent to public data members. This doesn't mean that there aren't perfectly lalid OO reasons for having them. For example you might want any caller to be able to setName in a person class but in a subclass you might want to screen for certain types of names.
The point is that when you are providing a public get and set method you should always think, "is there a way I can hide this" but sometimes you just need to have these methods and in many cases they cause no harm. There is no reason to be hostile towards them.
As for what to call the the experience how about "The OO epiphany"?
name withheld out of cowardice
Tuesday, February 24, 2004
> While you guys were arguing about the best way to architect the date class, I finished the app. :}
I used copy-and-paste to get your "under 65" logic into another class, *and* I bypassed your "under 65" logic when I implemented the new constructSelfFromXml method.
Christopher Wells
Tuesday, February 24, 2004
I'm not sure I can concur about having had an epiphany about OO. For me it came as a lot of small epiphanies about many of the qualities of OO, and I can see a strong synergy at the crossroads of these qualities. Those overall OO epiphanies I can recall didn't really pan out for me in the long run, yet I'm still a solid proponent of OO without having had a grand epiphany.
But I want to make a point about the specific example the original poster began with, because he describes a problem that is quite common owing to an unfortunate linguistic jump that took hold in our field. When we talk of "information hiding", this information we speak of is not the *data* inside our modules or objects. The information we should hide is the *knowledge* about processing details or design decisions that is not absolutely necessary for the user of the module to know. I think those authors and other people who often talked of "data hiding" missed the point entirely, and you can see it in their specific recommendations.
See D.L. Parnas as a start:
http://www.acm.org/classics/may96/
"Every module ... is characterized by its knowledge of a design decision which it hides from all others. Its interface or definition was chosen to reveal as little as possible about its inner workings."
The important point isn't to just prevent direct access of x with setX and/or getX, but to hide the knowledge about whether x even exists, if you could conceive of implementing your class without that x and have the new implementation be equally effective for a user of your class.
A good example is a String class. Does it have a length attribute? Surely yes, but that could easily be a computed attribute, depending on your usage patterns. If your first implementation exposes length as a data field, few languages will let you change that decision without requiring changes the users of that class. So we hide the information about whether length is a retained value or a computed value. What's important is that we hide the knowledge of our design choice, not that we protect access of the data slot.
veal
Tuesday, February 24, 2004
I have an unreasonable dislike for public data members. If I need something equivalent I always build getter/setters (although they are almost always inline).
I have a class that stores my programs configuration. It has a bunch of private members, some getters/setters for those members, an assignment and copy constructor, and methods for saving/loading to a stream. Some of the getters/settings do meaningful work on the data before storing it and some do not.
I like a consistent interface and it's a bit more future proof: If I need to add some processing I have the getters/setters to add it to.
Almost Anonymous
Tuesday, February 24, 2004
Grumpy Old Timer
I believe the epiphany you were discussing is commonly known as "Getting a clue." Not that they weren't good programmers before, but they got the idea that there wasn't any point in introducing extraneous code that could someday bite you in the backside.
For instance, in a class that represents users on my web site, the class is defined as such:
class User {
public:
string user_name;
string email;
string gecos;
string password;
void set_password();
};
Now, I could have created getter and setter methods for everything. But there wasn't really a lot of point. Pretty much any string is valid. The one place that there is a set method is for setting the password, because it needs special processing. The string that goes into the password needs to be spindled, folded and mutilated and a very special way to ensure that its value is useful to the web server. Notice though that the password is a public data member, because I will have cause to access it's encrypted value, and because there are times when I don't want the value to undergo the spindling or folder processes (it's a special case of mutilation).
Clay Dowling
Tuesday, February 24, 2004
I want to go a little further with what I said before, because it's related to a problem I see all the time.
"Information hiding" is perhaps just one flavor of a very important principle of design. It's important to identify decisions that are essential, and those that are accidental. That a particular parser parses (and requires) a stream of characters is probably essential. That those characters come from a file is usually accidental, in the sense that you could reasonably conceive of other uses. So if we define our parser with an operation or constructor that demands a Stream rather than a File or filename, then we can use it to parse in-memory character data too, and with no great pains.
veal
Tuesday, February 24, 2004
Grumpy O.T. -
I think what you're driving toward is this: a member function that fiddles one internal variable by itself with no coordination with other variables is really not much better conceptually than just setting the variable directly.
Validating the data value is just dandy but it has no sweeping value in a large design. Internal variables are an implementation detail.
Here's some help in the direction that I think you're seeking:
One clear advantage that is gained in OO development is that you can invent high level operations "against" objects which hide the internal mechanics. State variables are "internal mechanics". A good OO developer first of all invents classes that represent real world objects that are to be modeled; secondly that developer invents communication paths btwn the objects that represent the operations that can happen in the real world.
So, a classical and common example: say you have a class library that represents a GUI for displaying 3D objects on a viewing surface. Now: would a usable class library for this purpose burden you with methods for manipulating the displayed pixels directly?
No! Such a library would provide methods for creating new visible objects; for rotating the objects visually; for removing the objects; for changing the visual attributes of those objects. You'd (for instance) call a member function to create a cube of certain dimensions; set its color and size; set its angle in X/Y/Z; and so forth.
If the user of the GUI wants to put an object 'between' two other objects on the screen, then it would be useful to invent another high level operation (member function) to juggle the list of objects being displayed on the screen.
And so forth.
Each such member function will typically 'touch' and affect many internal variables and coordinate them.
So... maybe the solution to this is to talk through a non abstract example. The GUI was the easiest one I could think of that has enough complexity to it that even a newb should understand the advantages of wrapping details.
Bored Bystander
Tuesday, February 24, 2004
I think Veal said it best.
name withheld out of cowardice
Tuesday, February 24, 2004
I think a big step in OO evolution is "getting" design patterns and where and when they are useful.
For example, people new to OO will take obvious but less reusable approaches. Say you have an eCommerce site. The typical design will have a Product object with a constructor that takes a ProductID and pulls it from the database. Product(string productID). However, this ties the Product object inexorably to the DB implementation. You can now no longer use the Product object in any code seperate from the database libraries.
Instead, the better design is to have a seperate DataManager class implemented as a static-only class or as a singleton. Instead of calling "new Product(productID)", you call "DataManager.getProduct(productID)".
Likewise, you don't have a Product.RenderAsHTML() method. Instead, you might use a new ProductHtmlView class with a Product member.
Objects should only handle what they are responsible for. Just as a real product does not put itself on a shelf in a real store, the eProduct shouldn't be responsible for looking itself up in a MySQL database or rendering itself as HTML.
As far as getters and setters go, I like Properties as implemented in Delphi and C#. Syntactically, they're identical to use as data members, but they give you the same power as getters and setters. The only caveat is that you should not use Properties for expensive get/set operations because it will be non-obvious to people using the class until performance problems show up at runtime.
Richard P
Tuesday, February 24, 2004
Yeah, I also use get()s on stuff that should not be modifiable by clients of the class. Also, if I later decide to not just store something as a number but recalculate it from scratch, or better yet, cache it untill it is made dirty by other operations, I can change the implementation, but the access is the same. For that, getListLength(), etc is great. That's what encapsulation is all about.
Dennis Atkins
Tuesday, February 24, 2004
The sad thing is, in a decent OO language, the whole getter and setter thing is moot. Hell, even Visual Basic, which isn't exactly the paragon of OO perfection gets this right, with "property get" and "property set" methods. Methods should be for actual *operations*, not mere getting and setting, even if there are side effects that must occur when setting. Good OO languages hide this so that 'person.birthdate = aDate' calls a method if the implementation of Person desires that it do so.
Sadly, most of the "most popular" languages out there (C++, Java, and Perl) blow this big time.
Phillip J. Eby
Tuesday, February 24, 2004
>that it's no better than having public data members;
> and that it isn't OO.
I understand what you are saying, but they aren't
same enough to say they are the same.
Namely:
1. logical properties - the get/set interface has nothing
to do with your public members. By exposing public
members you've locked your self into a set of
attributes that may not even exist in later versions,
yet the information at the class level has is not the
same. It is encapsualtion to present an interface
for the class that is independent of the underlying
implementation.
For example, in a communication system the underlying
data is stored in a serialized buffer for quick transmisstion.
There are set accessors that write directly into the
buffer. There is not corresponding attribute to
publically expose.
For the data example, it's no business of a user how
you store the date. By exposing a Data object you've
locked yourself in.
2. validation - without a degree of indirection you can't insert your own validation layer. The date object, for
example, can't know the domain validations associated
with the class so the validations can't really be put in
that date class.
3. event propagation - if you have observers you
need to say the object has changed. Nobody wants to
know a contained date object has changed.
son of parnas
Tuesday, February 24, 2004
public class person
public int age
get: return CalcAge( _dob )
private date _dob;
private int CalcAge( date DateBorn )
return InYears( TodayDate - DateBorn );
constructor Person ( DateBorn )
create
_dob = DateBorn
*--------------------------------------------------*
Me = new Person ( strToDate( "7/15/1972" ) );
Print Person.Age;
Yo
Tuesday, February 24, 2004
In my experience, there's actually *three* levels to C++ programmers.
The first two are, as you said:
1) C++ as a better C - procedural programming
2) C++ as an OO language
The third is when they start grokking templates as something more than just list< T >... generic programming.
That third step is something that many C++ developers never get to, and some who do (like myself) regret ever taking that step, since nobody around here seems to understand what I'm talking about anymore. :-)
Chris Tavares
Tuesday, February 24, 2004
"The third is when they start grokking templates as something more than just list< T >... generic programming."
I got my feet wet in that and then decided it's not worth the headaches. I debugged a bit of template-hell and now I'm satisfied with only going as far as list<T>!
Almost Anonymous
Tuesday, February 24, 2004
Bah template meta-programming. Its an accident of the design of templates, and shouldn't be used in production systems in my opinion. *much* to hard to get right.
And for those not familiar with TMP, I don't mean list<T> I mean stuff like if, while, and other statements that run during compile time implemented with templates. Nasty stuff.
Andrew Hurst
Tuesday, February 24, 2004
"In my experience, there's actually *three* levels to C++ programmers."
Actually, there's fifteen. But no one will get higher than the sixth level until our species evolves further.
Kyralessa
Tuesday, February 24, 2004
Actually Chris, many of us "grok" templates but are just not convinced that it is generally a good idea.
Wednesday, February 25, 2004
I often use getters and setters. I often find that I have more getters than setters. And that setters validate their content. Etc.
I also find that lots of my getters are for values that are derived or calculated from internal values, not the pure internal value itself.
Why do people hate getters and setters? The only ugly thing about it is the wording. In Object Pascal (Delphi etc), you have a much neater 'property' keyword. Worth looking at to marvel at it's elegancy, but essentially the same thing.
i like i
Wednesday, February 25, 2004
It took me a while to switch over to the OO way of thinking from procedural pre-history. I saw it as a series of conceptual breakthroughs punctuated with the odd 'eureka' moment.
My experience is Delphi-centred, though not entirely, and I like Delphi properties because they hook everything up in a nice conceptual way.
Ian Sanders
Wednesday, February 25, 2004
"I was trying to explain to a new C++ programmer that having public "get" and "set" members is not encapsulation; that it's no better than having public data members; and that it isn't OO."
Think of a class instance as yourself and the calling code as your manager. Which is a more productive way to work: having your manager give you a task to perform and leave you to get on with it, or having them sit on your shoulder and micromanage your every single move?
Developers have specialized skills and knowledge, making each one an expert in their particular field. Managers have responsibility for assigning work to developers and overseeing the smooth and efficient running of the department in general. It's the same with code. Modular design is all about passing the buck. When structuring code, delegate tasks that require expert skills to perform to subroutines and functions, and those that take both expert skills and knowledge to carry out to classes/objects/closures. In your program, these structures are the specialists; the code that calls them, the generalists.
...
Or: Never do yourself what you can offload to others. Ignorance and laziness ARE virtues, you see. :)
has
Wednesday, February 25, 2004
Now take even one more step and imagine having no managers, but just leaders of the moment emerging from among those specialists and other doers as the flow warrants, and watch your development soar.
Oh, were we still talking about OO programming? I got distracted by thoughts organizational efficiency in the workplace. :-)
veal
Wednesday, February 25, 2004
Mssr Tavares,
I am unfortunately stuck at position 2 in your hierarchy. I mean, I can write a template if I have to, but either I'm writing very simple software were the benefits just never appear (possible, even likely), or I haven't gotten it.
Could you point me to some resources that would educate me further in the matter? When I read about them in Stroustrupe they certainly looked powerful, and I know that there have been situations in other positions, in other programming languages, where I would have liked such a thing. The C++ systems I've been building have been simpler though (perverse that; using the language that handles complexity better, I write simpler software), and I feel I've been missing something.
Clay Dowling
Wednesday, February 25, 2004
Well, if you *really* want to know what templates can do, get a copy of "Modern C++ Design" by Andrei Alexandrescu:
http://www.bookpool.com/.x/ehqeo57dv1/sm/0201704315
He pushes templates further than anybody else on earth. You'll be impressed by what you can do at compile time, and probably just a little bit scared too. :-)
Another book (which should be on your bookshelf in any event) is "Exceptional C++" by Herb Sutter:
http://www.bookpool.com/.x/ehqeo531t6/sm/0201615622
This book is not just templates, but it does go into a lot of details on what you can do with them.
Be warned; you need a very up-to-date compiler to do any of this stuff. On Windows, you need VS.NET 2003; 2002 or 6.0 won't do it. I don't know which version, if any, of gnu C++ supports this.
Chris Tavares
Wednesday, February 25, 2004
I "got" the idea of object-oriented programming many years ago. As in, understood that OOP meant putting behavior and attributes in the same place, and polymorphism/encapsulation/inheritance. I'm still learning how to do it. As with anything, improvement requires constant learning and experimentation. Being exposed to new ideas, trying them out, fitting them into your mental framework. Learning what works and when.
For me, as someone else mentioned, it's been a whole bunch of little "a-ha's". No great epiphany, revelation, realization or lightbulb moment.
Should be working
Wednesday, February 25, 2004
Chris,
Thanks for the pointers. I'll look into those books. The Sutter book looks particularly interesting. I am, alas, completely self taught and lacking in mentors for C++ and object oriented programming in general. The perils of a one man shop.
Clay Dowling
Wednesday, February 25, 2004
"Oh, were we still talking about OO programming? I got distracted by thoughts organizational efficiency in the workplace. :-) "
Reckoned y'all would appreciate the metaphor...:)
has
Thursday, February 26, 2004
Recent Topics
Fog Creek Home
|