Object Oriented Programming has been the standard for the industry for decades now. However, as implemented in the various object oriented programming languages, it’s more complex and difficult to manipulate than commonly understood. Having worked with a number of object oriented programming languages and non-OOP languages, I thought I’d state the case for avoiding the languages altogether due to their complexity.
Now, before we get into the points, I want to emphasize that I like the core ideas of object oriented programming, as a rule. Many of these principles are natural extensions of common practice, and when properly implemented they make it possible to produce more complex code with greater functionality and usability. However, as implemented in the various languages (I am most familiar with C++ and Java), they cause more problems than they solve. And any technique that makes programming more difficult and functionality more limited is fundamentally wrong.
Rules Based vs. Design Based
Once, life was comparatively simple in the world of programming. In the old days, using languages like C or Pascal, the rules were basically pre-defined and simple. So long as your code complied with these rules (usually regarding basic syntax structures, pointer use, and typing restrictions), you had nothing but your design to worry about.
Not so, Object Oriented Programming languages. Thanks to dynamic overloading of operators, you spend a lot of time defining new rules and keeping track of what rules are in play at any given time. These languages complicate things immensely; for example, if you were to look at the simple instruction:
a = b + c;
You have no idea what it means. Sure, so long as you don’t overload anything and all the variables are integers, you can easily read the command. But in object oriented programming languages, there is no guarantee that the variables are any of the numeric types, or that the operators are even what you thought. This instruction might actually take an email address “c” and a file “b” and send it using encryption method “a”, or it might take two scripts and interlace them, compile them, and then run a script to execute them in a desired manner.
If you don’t know the current rules you defined and the meanings of each variable, you can quickly run yourself into deep trouble. Worse still, psychiatry has determined that the average person can keep 7+/-2 (that is, five to nine) distinct ideas in mind at once, so if this were any longer it could quickly become impossible for any programmer to keep track of the rules. This is one of the biggest flaws in the object oriented programming language design, because it fundamentally opposes human capabilities.
This is basically the problem of polymorphism, but there are other mechanisms by which the ruleset can be increased, particularly in object oriented programming languages.
Another key issue in object oriented programming language design is the issue of inheritance. Many thousands of posts and lectures have covered the problems with multiple inheritance, so we will not go into detail here, but suffice it to say it’s a major problem. But inheritance itself can have some problems.
Fundamentally, top-down inheritance (where parent objects are passed down into child objects) is a simple and obvious design. Classically, a C programmer would put the “parent” structure as the first element of a “child” structure and cast the child as the parent whenever the inheritance is called for. It’s simple and effective design, but object oriented programming languages convolute things by combining functions with structures within a class. This means that, in order to find what functions a child object actually possesses, you have to backtrack through all the parents and conglomerate all those things together. The longer the inheritance chain, the longer the chain a programmer must backtrace and bring together. So, while it seems simple on its face, ultimately it necessitates IDEs or independent graphs to keep all these functions straight.
Furthermore, we again come to the problem of rule-based design. With overloading, two functions with the same name may operate differently between parents and children. That means that you have to add to the difficulty of keeping track of the function stack the new problem of keeping track of which functions operate differently. Rules upon rules makes the problem more complex, and the result is that the average programmer (and even many experts) require further tools just to keep their own designs straight. That makes object oriented programming languages more difficult to master and employ than simpler functional and structural languages.
Finally, inheritance involving abstract objects puts another complication on top of the stack. Because object oriented programming languages tend to include and encourage abstract classes, which are designed not to be implemented, overloading is required for each of the children to properly implement their parent functions. This means that abstract classes force those issues with rules based design and polymorphism, which we have already discussed as unnecessarily confusing to the human brain.
Again, on its face, abstraction is a reasonable and useful approach to design. Often enough we run into problems where the simplest solution is to teach the computer, to one degree or another, to come up with its own solutions to problems. In a C context, this is usually where we apply callbacks and function pointers, an ugly but reasonably elegant solution to the issue.
However, when we get to the point of templating, we take a step too far. The abstraction makes some sense on a higher level – you create a basic framework to work with objects, then the compiler makes it all happen. The problem with this is that there is no code to debug, and that’s a real problem inherent to this object oriented programming design choice. The inherent degree of abstraction becomes a hindrance to maintenance, and as a result programming becomes more difficult with these abstraction elements.
In almost all real world applications, we want to restrict access to some data elements. Without such precautions, it becomes entirely too easy to accidentally introduce user errors into your complex systems, and these errors can be extremely difficult to correct and highly costly during the repair. So data hiding is almost intuitively necessary in real world applications.
However, the common mechanisms employed in object oriented programming languages have some real issues. All too many programmers will create private or protected data elements, then implement functions that functionally render them public. While this is more a programmer error than an error of the language, it is an error so commonplace as to be a functional problem of the languages.
Furthermore, object oriented programming languages contain function hiding. In the same way data hiding aspects are mishandled, private functions are often numerous, trivial, and implemented only once in a public function. This commonplace design pads code count at the expense of essentially adding trash to the documentation and code.
Binding Functions to Data
An issue inherent to most object oriented programming languages but not necessarily apparent in the philosophy of oop is the foundational binding of functionality to data. This is a problem of design philosophy moreso than object orientation itself, but it is a problem nonetheless.
Data is essentially static and stored in its own aspects of memory. Whether allocated to the stack or the heap, all data elements are in one place and are preferably of a fixed size. In essence, data represents things, and all things belong together.
Functions, on the other hand, are mechanisms by which data and operational mechanics are modified. These elements are fluid and disconnected from individual data elements, because they are transformations and operations rather than things. Instructions, then, are logically and often physically completely separated from the data.
In object oriented programming languages, the two are tightly bound. Classes are combinations of data objects and functions designed to manipulate them, which logically binds two entirely different things together. Now, it is true that among the oldest arguments in philosophy is the argument over whether things are defined by a permanent “is” or their ability to change, but in programming this problem is basically solved – data is, and functions change. In binding the two so tightly, a logical barrier is destroyed and two disparate topics tend to intermingle in the programmer’s mind.
It’s also a problem for reusability that object oriented programming languages do not like to address. Often classes will contain similar elements that are manipulated in exactly the same way, but because of the bond between functions and data the only options are copy-paste and unnecessary (and logically meaningless) inheritances. Of course, if you are willing to make your data elements public, it is possible to employ the old technique of defining highly-reusable library-quality functions outside of classes, but in the object oriented programming philosophy this is often considered distasteful practice.
Unfortunately, there are precious few solutions possible in most object oriented programming languages. In C++, you can write all your code as though it were C with reasonable effectiveness, but at that point there is no reason to use the more complicated compilation and implementation of C++ over C. In Java, on the other hand, the design principles are so foundational to the language that it is essentially impossible to create large projects while avoiding these complications.
The solution I employ is to embrace the philosophical elements of object oriented programming in my C code. I have written about the ways I implement data hiding, abstraction, and inheritance, and the judicious use of function pointers and callbacks creates sufficient polymorphism to solve problems without unduly complicating the system and language. While I mostly write C code, structural coding principles usually make allowances for object oriented design principles (in fact, the oop principles were designed during the reign of structural programming).
Besides this, the only reasonable solution is to discover a way to design object oriented programming languages without these complications. I don’t know a good way to do this, because those very things make them distinct from the structural languages, but perhaps solutions could be made to exist. I leave this challenge as an exercise to the reader – if you make it work, there might well be a market out there for you.