A common question among newer Object-Oriented Programmers is "Why should I use and prefer to use getter and setter methods for my objects variables when I can simply set them as public?" For example, let's consider the trivial object below:
[highlight=C++]class myObject
{
int myVar;
public:
myObject() : myVar(0)
{
}
int getMyVar()
{
return myVar;
}
void setMyVar(int x)
{
myVar = x;
}
};[/highlight]
It would appear to a casual observer, and could be pretty well argued, that there's no need to have two methods just to set one variable and get one variable inside of an object, aside from the knee-jerk reaction to encapsulate everything. A public variable would accomplish the same goal, and would be far easier to read and write, like so:
[highlight=C++]class myObject
{
public:
int myVar;
myObject() : myVar(0)
{
}
};[/highlight]
To someone who hasn't experienced any problems with this way of doing things, it may seem odd to want to write more unnecessary code when all this time we've been trying to find ways to avoid writing more code. The aforementioned way looks more computationally expensive, harder to understand, and completely unnecessary.
Well... no, it isn't. Encapsulation is the basic principle that teaches us to always do this, but we're not writing all of this just to appease the Encapsulation Police, we're doing it for a very good reason, and that's flexibility with our objects. I'm going to venture a proposition... never in the history of programming has there ever once been someone who completely wrote a non-trivial object and didn't need to change it ever again. What I am saying is that I guarantee to you you'll find yourself in situations where you're going to need to change some internal mechanisms of an object to add functionality, or make it more efficient, or for any other assorted reason. I'll now prove this to you.
Let's continue to utilize the object above, but let's say, for some reason, you need a new function that "rewinds" and "fast forwards" the returned value from getMyVar(), so you'll need to keep some kind of internal mechanism as to how to rewind/FF it. Let's also say that you need to keep count of how many times myVar was changed, as well as add a new overloaded constructor to allow the user to set any value as the initial value (instead of just 0).
[highlight=C++]class myObject
{
unsigned long tChanged;
bool looped;
std::vector<int> myVars;
std::vector<int>::iterator retNum, endPoint;
void setup(int x)
{
myVars.reserve(20);
myVars.push_back(x);
retNum = myVars.begin();
endPoint = myVars.end();
}
public:
myObject() : tChanged(0)
{
setup(0);
}
myObject(int x) : tChanged(0)
{
setup(x);
}
int getMyVar()
{
return *retNum;
}
/* I wanted to ensure I didn't have to do any additional
dynamic memory allocation for this method in the case
of setMyVar being called more than 20 times, so I made
it so after the 20th call, setMyVar simply wrapped
around the vector and treated the first number as if
it were the 21st, but that means rewind can't go further
back than 20 numbers. That's okay.*/
void setMyVar(int x)
{
if (myVars.size() >= 20)
{
if (endPoint >= myVars.end())
{
endPoint = myVars.begin();
looped = true;
}
*endPoint = x;
++endPoint;
}
else
{
myVars.push_back(x);
endPoint = myVars.end();
}
retNum = endPoint - 1;
++tChanged;
}
void rewind()
{
if (retNum >= endPoint)
{
if (--retNum < endPoint)
retNum = endPoint;
}
else
{
if (--retNum < myVars.begin())
{
if (looped)
retNum = myVars.end() - 1;
else
retNum = myVars.begin();
}
}
}
void rewind(int x)
{
for (int iii = 0; iii < x; ++iii)
{
rewind();
}
}
void fastForward()
{
if (retNum < endPoint)
{
if (++retNum >= endPoint)
retNum = endPoint - 1;
}
else
{
if (++retNum >= myVars.end())
{
retNum = myVars.begin();
}
}
}
void fastForward(int x)
{
for (int iii = 0; iii < x; ++iii)
{
fastForward();
}
}
unsigned long getTimesChanged()
{
return tChanged;
}
};[/highlight]
As you can tell, this object has become much less trivial! But here's the real advantage to using your getter/setter methods: Any other code you made that utilized the myClass object doesn't have to change at all and it is still fully functional! While your getter and setter methods have wildly changed in implementation, and new methods have been added to the object, your older code that still used the old getter/setter methods will still be able to use this object exactly as if nothing had changed at all, and you won't need to worry about updating your entire code base to reflect what could be only one or two changes in a single object! This is absolutely vital in any project with more than a few source code files, since tracking down individual changes can be a difficult and time-consuming task, as well as could produce many difficult to find, and fix, bugs!
When people talk about flexibility with code, and a need for abstraction, this is a great example of what they mean. While the ideas of encapsulation and abstraction extend far beyond this simple example, this is a very much real code example as to what's going on, and why it is so important to do. Imagine if you had chose to instead go with a public variable that could be changed by any object, by doing so you've suddenly lost any flexibility to count the number of times that public variable has changed, or to extend the functionality of the object with that variable. You've lost a significant amount of versatility in your code, and through experience many developers have learned that this is the best way to go about things. That is why we use getter and setter methods!
To close, I want to address some of the challenges to getter/setter methods, and emphasize when using these methods is appropriate. Remember that divulging implementation details, such as what certain internal "has-a" objects contain, or what those objects are doing and what their states are, is a completely inappropriate use of getter/setter methods. Objects in themselves should be doing that kind of legwork, not the other objects using them. The article writer asserts that all getter/setter methods break encapsulation and provides no evidence of this supposedly obvious truth, but it doesn't. The reason these methods exist is to maintain encapsulation, as I've just shown. The entire implementation can be changed in the object, and so long as there's something to capture those get/set methods, then things will continue to work. Getter/Setter methods are intended to be part of the interface of an object, and part of it's functionality, not an exposure to implementation details, and this distinction is important.
Hope this all helped!
Wow I changed my sig!
I think the best argument for setters/getters is this: if you expose a variable, you are making a contract with your users that all future iterations of your class will have that variable available. You just reduced your ability to alter the internal representation of your class!
Better yet, you should be able to publish only a class with no data members and inherit from it as an interface!
+rep
This was a good read, +rep
Interested in participating in community events?
Want to harness your programming skill and turn it into absolute prowess?
Come join our programming events!
Very good argument, and very true. I've actually completely rewritten a class that spanned a good thousand lines of code (wasn't an incredibly enjoyable experience) but made sure to keep all the public methods the same and I didn't have to change anything else in the code
.
+rep
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks