Why not make everything public in a class?

Started by
14 comments, last by Zahlman 19 years, 4 months ago
Quote:Original post by Meta Adam
Yea, but i dont understand why it couldnt just be:
*** Source Showing data members as public ***


Well, it _can_ be. That's what makes the concept difficult. I was a longtime C programmer when I started to learn C++. The first thing that just baffled me was why you wanted to add all this complexity.

Why did these classes need methods? I have written functions and they work fine.

What's with this access protection stuff? I have declared variables and they work fine.

Making the data that a class holds private is a matter of methodology, not syntax. In other words, the code you posted works fine. However, what programmers discovered over the years of building massive applications with "top-down" languages like C was that it was really hard to manage and debug the code base.

The idea of C++ and being Object Oriented in general is that you can break your program's functionality into classes. The classes control their internals (the data) and it exposes functions that others can use to operate on them.

The string example given above is a classic. If you wrote a class that held a string for you, you would do so because managing character buffers is a pain, and prone to bugs. So you write this class, but if you simply left out the data in "public" for anyone to mess with, then you can't ever be certain the data in the string class still has any integrity. After all, any other class or function or method could come along and change the length of the string without changing the string data to match.

Instead you write public methods that make sure all operations performed are valid. They do some sanity checking, change the internal data, and will give it back to you with public accessors like get.

In a small class, the point can not be driven home. Your example makes the whole process of having private data with public accessors seem like extra and unnecessary work. That's why I balked so much when I read my first C++ book. The examples weren't compelling enough.

I am not belittling your example. Rather, I am reliving the feelings I had when I first read the chapter on data encapsulation in my first C++ book and said "What is this useful for again? It seems like more work."

That's the best answer I can give. The code you post works. However, it does not encapsulate the data. So any function that wants to change the dog's height, weight, or age can do so without anything to stop it. And if that's okay, then public is fine.
Advertisement
In this specific example, it really doesn't matter: your class is a mere data aggregation. One typical design error people make is to make their member variables private and then hurry to write accessors for each and every one of them.

That's not a good idea.

In fact, it only serves to make things worse.

Sure, they will argue that it allows them to later change how things work (encapsulation) without having to modify external code (e.g. to add parameter checking...), but in practice, they tend to provide many more accessors than are really needed, and end up exposing way too much of their class' implementation details.

Yes, even knowing that your class has a data field named 'age' is a piece of information you are revealing to the world.

A better practice is to expose operations on the class rather than those kind of get/set accessors, and to write client code in terms of those operations rather than in terms of the data in the class. The only things that really need access to your data fields are display functions (and even then...) and GUI builder tools (so that they can set the properties on each GUI component from within the GUI building application, and have the components immediately change). Most of your code shouldn't need to access those data fields directly, and you should consider making code that does part of your class.

As a small example, instead of letting people manipulate the age of your dog, you could store the date of birth at construction and compute the age upon request... (though you could use a private variable to cache the computation... people don't have to know - that's why it is private; having a set_age function pretty much implies there is an age variable).

edit: building on JimPrice's example, instead of a set_health() function, your tank class might have take_damage() and get_repaired() functions. get_speed() might work from a base_speed member variable, accounting from the damage the tank has suffered. Abstracting things that way mean you never have to explicitely reference a variable.

One final minor nit, I'm personally fond of this syntax, when accessors are necessary (and, yes, they sometime are, just not as often as people seem to believe):

class Foo{  Bar bar_;public:  const Bar& bar() const     // Note that the member function is const, so that                             // it may work even on const Foo objects.  {     return bar_;             // Return by const reference, disallows modification                             // You may return basic types (e.g. int) by value.  }   Foo& bar(const Bar &value) // Take parameter by const reference, see above  {    bar_=value;     return *this;            // Return the object itself as a reference  }};


Returning the object as a reference allows chaining: f.foo(10).bar(20);, in a manner very similar to chained assignment f = g = h; (which is why operator= and its variants +=, -= ... do return *this; by reference too).
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by Fruny
Making things private is thus a way to protect your class invariant, as well as hiding implementation details by forcing people to use a specified interface (Encapsulation).


This is a common explanation, but in my opinion, it is backwards and counter-productive.

First of all, you are not protecting the invariants -- you are protecting the user of the class. You are protecting the user from changes to the interface of the class. If the user wants to fiddle with implementation details and suspend (temporarily, of course) the validity of the class invariants, then more power to him. The real problem is that if the implementation of the class is exposed, then every time the implementation changes (even by the smallest amount), the interface changes, and that could have serious consequences.

Second, the reason for hiding details is not to force the user to use the class in a certain way. The reason is to clear the interface of distractions and complication. An interface that exposes implementation details requires the user to understand those implementation details in order to use the class. An interface should expose only what is necessary to use the class. Anything more just makes the class harder to use. Besides, nobody likes to be forced to do anything.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Quote:generally I don't bother with get/set. A single overloaded access function for each property is enough:

int age();
void age(int);


That is Get/Set, just with a different set of function names!
I'm growing tired of this quesion...
Read older forum topics, or [google]it, or read a book about it.

Please excuse my unusual grumpiness, I got horribly sun-burnt today!
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by JohnBolton
Second, the reason for hiding details is not to force the user to use the class in a certain way. The reason is to clear the interface of distractions and complication.


Exactly - it's to not force the user to use the class in the other way. ;)

Quote:Besides, nobody likes to be forced to do anything.


Yeah, if going through your class' interface feels 'forced' for some reason, that's a smell that something is wrong. You should want to only do things in the prescribed manner. Especially if you own the code on both ends. :)

@leiavoia: There are two sides of the debate on accessors and mutators - one is the extent to which they are used, and the other is what they are called. I agree that sounds kind of silly, but there you have it. FWIW, my policy is:

- If there's an invariant to be maintained, then there's probably a better name that doesn't necessarily even mention the variable name.
- Having only one of {accessor, mutator} is better than having both, although still inferior to having methods that Do Something(TM).
- If I really really want to provide both methods, then I normally just make the member public instead - and leave a TODO refactoring comment. I've always found a way to make it work better (except in Python, where I often don't even try because the project just isn't ever going to get bigger than a couple hundred LOC anyway), just like I've never needed or wanted a goto (except of course in languages where that's all you've got - and although I appreciate that it must make life a lot easier for some, I can only conclude that they're doing something radically different...).

This topic is closed to new replies.

Advertisement