Moving Beyond the OOP Obsession

Articles pointing out the foibles of an object-oriented programming style appear regularly, and I'm as guilty as anyone. But all this anecdotal evidence against OOP doesn't have much effect. It's still the standard taught in universities and the go-to technique for most problems.

The first major OO language for PCs was Borland Turbo Pascal 5.5, introduced in 1989. Oh, sure, there were a few C++ compilers before that, but Turbo Pascal was the language for MS-DOS in the 1980s, so it was the first exposure to OOP for many people. In Borland's magazine ads, inheritance was touted as the big feature, with an example of how different variants of a sports car could be derived from a base model. What the ads didn't mention at all was encapsulation or modularity, because Turbo Pascal programmers already knew how to do that in earlier, pre-object versions of the language.

In the years since then, the situation has reversed. Inheritance is now the iffiest part of the object-oriented canon, while modularity is everything. In OOP-first curricula, objects are taught as the method of achieving modularity, to the point where the two have become synonymous. It occurred to me that there are now a good many coders who've never stopped to think about how modularity and encapsulation could work in C++ without using classes, so that's what I want to do now.

Here's a classic C++ method call for an instance of a class called mixer :

m->set_volume(0.8);

m is a pointer to instance of the mixer class. set_volume is the method being called. Now here's what this would look like in C++ without using objects:

mixer_set_volume(m, 0.8);

This is in a file called mixer.cpp , where all the functions have the mixer_ prefix and take a pointer (or reference) to a variable of the mixer type. Instead of new mixer you call mixer_new . It might take a moment to convince yourself, but barring some small details, these two examples are the same thing. You don't need OOP to do basic encapsulation.

(If you're curious, the pre-object Turbo Pascal version is almost the same:

mixer.set_volume(m, 0.8);

mixer is not an object, but the name of the module, and the dot means that the following identifier is inside that module.)

Now the C++ mixer_set_volume example above is slightly longer than the class version, which I expect will bother some people. The mild verbosity is not a bad thing, because you can do simple text searches to find everywhere that mixer_set_volume is used. There is no confusion from multiple classes having the same methods. But if you insist, this is easy to remedy by making it an overloaded function where the first parameter is always of type mixer . Now you can simply say:

set_volume(m, 0.8);

I expect there are some people waiting to tell me I'm oversimplifying things, and I know perfectly well that I'm avoiding virtual functions and abstract base classes. That's more or less my point: that while simple, this covers the majority of use cases for objects, so teach it first without having the speed bump of terminology that comes with introducing OOP proper.

To extend my example a bit more, what if "mixer" is something that there can only be one of, because it's closely tied to the audio hardware? Just remove the first parameter to all the function calls, and you end up with:

mixer_set_volume(0.8);

You can teach this without ever using the word "singleton."

(You might enjoy Part 1 and Part 2 of this unintentional trilogy.)

permalink March 27, 2016

previously