|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
There are things that are objects. Things that have state and change their state are objects. And then there are things that are not objects. A binary search is not an object. It is an algorithm. Alex Stepanov IntroductionMost of us who use object oriented languages tend to think that non-member functions are a procedural programming tool that has no place in OOP. In this article, I will try to show that non-member functions are still very useful, and that by using non-member functions you can improve design of your OO code. This is by no means a criticism of object oriented programming in general. On the contrary, my aim is to show that non-member functions can fit into OO design just fine. I believe that careful use of non-member function can:
Note that I never talk about global functions here. For anything but the very small projects, I strongly advocate use of namespaces in order to avoid global namespace pollution and name clashes. Non-member functions in actionContrary to what "OO-purists" would like us to believe, not everything is an object. Yes, when you have some data, and some operations on that data, it makes perfect sense to wrap them into a class. However, there are operations that deal with different data at the same time, and in that case it may be more logical to implement this operation as a non-member function. ReadabilityLet's illustrate my point with an example: class sample_string { std::vector<char> data_; public: explicit sample_string (const char* cstr); int get_length() const; char get_at (int index) const; void append(const sample_string& other); sample_string concat (const sample_string& other); }; We are going to discuss which of the member functions should be non-members. But, before we do that, let's try to answer the ultimate question: Why did we make class Back to our sample. Our data is made private, and this is good. Now we are in charge of how the data is accessed, and this was the primary goal of making this class. Now, what about member functions?
If we look at the way function sample_string a , b; ... a.append(b); The syntax suggests us that we alter the state of object Now, the function sample_string a, b; ... sample_string c = a.concat(b); By looking at the syntax, we could come to the wrong conclusion that this function somehow alters the state of object Let's implement this operation as a non-member function.: sample_string concat (const sample_string& left, const sample_string& right); This operation would be used like this: sample_string a, b; ... sample_string c = concat(a, b); This time, the syntax suggests that we used some information from objects Examples like this can be found in many real-life libraries and applications. For instance, take a look at .NET FCL String __gc* Insert (const String __gc* source, int startIndex, const String __gc* value); While the name of the function is still somewhat misleading, the signature leaves no doubt what really happens. EncapsulationWhat about encapsulation? A lot has been written about how the usage of non-member functions improves encapsulation, but it comes down to this simple statement: non-member functions cannot access internal class implementation. If you can implement a functionality without messing with internal implementation, then it means better encapsulation. Of course, this does not mean that we should try to implement each functionality as a non-member function in order to improve encapsulation. Sometimes a function needs direct access to internal class data, and in that case it may be necessary to implement it as a member. Even if a function needs read-only access to class data, in many cases it is a better idea to implement it as a member function. Introducing an accessor function to internal class data in order to implement a function as a non-member, is almost always a bad idea. For instance, in our example with class //this version returns a copy of the data std::vector<char> sample_db::get_data() const { return data_; } Of course, it can be unacceptable to copy data like this in terms of performance, but if we talk about encapsulation, this is a good solution. However, if the accessor returns a reference or a pointer to the data, then we actually hurt encapsulation. //this version returns a reference to the data const std::vector<char>& sample_db::get_data() const { return data_; } If we do that, we expose our class data directly to the class users, and that is bad. Imagine that at some point we decide to change internal class data to be CouplingFinally, let's see how the use of non-member functions affects object coupling. It is a good design practice to promote loose coupling between objects. The less objects know about each other, the better. On the other hand, objects need to interact in order to make system functional. To solve this problem, Mediator Design Pattern has been introduced. Mediators are classes that handle interaction between different types and thus promote loose coupling. However, there are cases when a non-member function is enough to serve the role of a mediator. For instance, assume we have a class class sample_db {...};
Now, imagine that in an application of ours we need to write class sample_db { ... public: void write_sample_string (const sample_string& str); }; That would work, but it requires void write_sample_string_to_db (const sample_string& str, sample_db& db); Of course, in order to make this work, Political incorrectness of non-member functionsSo far, we have discussed only the technical side of the problem, and in ideal world that would be enough. Alas, we live in real world, and if you decide to use non-member functions in our programs, you may experience other kinds of problems. Namely, many people still think that non-member functions are a legacy from C that has no place in object oriented programming practices. This can raise several issues:
ConclusionI hope this article will make some developers reconsider use of non-member functions in their OO programs. However, the last thing I want is to turn anybody from a "OO zealot" to a "non-member functions zealot". Be aware that there are cases when use of non-member functions makes sense, and cases when it does not. Use your knowledge, experience and instinct to decide when to implement operations as member functions and when to leave them out of classes. Always bear in mind that your ultimate goal is to produce high-quality code, and for that purpose use any programming technique that you believe will be appropriate for your specific tasks. Don't be a slave to any programming paradigm. Make them serve you. References
| ||||||||||||||||||||